shiori-sdk 3.1.0__tar.gz

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.
Files changed (121) hide show
  1. shiori_sdk-3.1.0/LICENSE +21 -0
  2. shiori_sdk-3.1.0/PKG-INFO +384 -0
  3. shiori_sdk-3.1.0/README.md +364 -0
  4. shiori_sdk-3.1.0/pyproject.toml +40 -0
  5. shiori_sdk-3.1.0/python/shiori_sdk/__init__.py +8 -0
  6. shiori_sdk-3.1.0/python/shiori_sdk/_version.py +4 -0
  7. shiori_sdk-3.1.0/python/shiori_sdk/accounts/__init__.py +25 -0
  8. shiori_sdk-3.1.0/python/shiori_sdk/accounts/capability.py +124 -0
  9. shiori_sdk-3.1.0/python/shiori_sdk/accounts/models.py +223 -0
  10. shiori_sdk-3.1.0/python/shiori_sdk/accounts/rules.py +50 -0
  11. shiori_sdk-3.1.0/python/shiori_sdk/accounts/targets.py +158 -0
  12. shiori_sdk-3.1.0/python/shiori_sdk/bridge.py +54 -0
  13. shiori_sdk-3.1.0/python/shiori_sdk/channel_events.py +73 -0
  14. shiori_sdk-3.1.0/python/shiori_sdk/channels/__init__.py +73 -0
  15. shiori_sdk-3.1.0/python/shiori_sdk/channels/avatars.py +15 -0
  16. shiori_sdk-3.1.0/python/shiori_sdk/channels/chat_id_command.py +51 -0
  17. shiori_sdk-3.1.0/python/shiori_sdk/channels/chat_types.py +176 -0
  18. shiori_sdk-3.1.0/python/shiori_sdk/channels/context.py +56 -0
  19. shiori_sdk-3.1.0/python/shiori_sdk/channels/errors.py +9 -0
  20. shiori_sdk-3.1.0/python/shiori_sdk/channels/group.py +14 -0
  21. shiori_sdk-3.1.0/python/shiori_sdk/channels/hooks.py +31 -0
  22. shiori_sdk-3.1.0/python/shiori_sdk/channels/identity.py +14 -0
  23. shiori_sdk-3.1.0/python/shiori_sdk/channels/identity_index.py +27 -0
  24. shiori_sdk-3.1.0/python/shiori_sdk/channels/message_source.py +267 -0
  25. shiori_sdk-3.1.0/python/shiori_sdk/channels/pairing_command.py +38 -0
  26. shiori_sdk-3.1.0/python/shiori_sdk/channels/projection.py +68 -0
  27. shiori_sdk-3.1.0/python/shiori_sdk/channels/reply_context.py +110 -0
  28. shiori_sdk-3.1.0/python/shiori_sdk/channels/services.py +149 -0
  29. shiori_sdk-3.1.0/python/shiori_sdk/channels/session_key.py +19 -0
  30. shiori_sdk-3.1.0/python/shiori_sdk/channels/threads.py +18 -0
  31. shiori_sdk-3.1.0/python/shiori_sdk/commands.py +32 -0
  32. shiori_sdk-3.1.0/python/shiori_sdk/context.py +13 -0
  33. shiori_sdk-3.1.0/python/shiori_sdk/diagnostics.py +33 -0
  34. shiori_sdk-3.1.0/python/shiori_sdk/errors.py +49 -0
  35. shiori_sdk-3.1.0/python/shiori_sdk/event_binding.py +26 -0
  36. shiori_sdk-3.1.0/python/shiori_sdk/extensions.py +97 -0
  37. shiori_sdk-3.1.0/python/shiori_sdk/files/__init__.py +1 -0
  38. shiori_sdk-3.1.0/python/shiori_sdk/files/assets.py +32 -0
  39. shiori_sdk-3.1.0/python/shiori_sdk/files/json.py +128 -0
  40. shiori_sdk-3.1.0/python/shiori_sdk/files/paths.py +24 -0
  41. shiori_sdk-3.1.0/python/shiori_sdk/files/text.py +31 -0
  42. shiori_sdk-3.1.0/python/shiori_sdk/http.py +82 -0
  43. shiori_sdk-3.1.0/python/shiori_sdk/json.py +52 -0
  44. shiori_sdk-3.1.0/python/shiori_sdk/lifecycle.py +184 -0
  45. shiori_sdk-3.1.0/python/shiori_sdk/mcp.py +48 -0
  46. shiori_sdk-3.1.0/python/shiori_sdk/media.py +17 -0
  47. shiori_sdk-3.1.0/python/shiori_sdk/memory/__init__.py +1 -0
  48. shiori_sdk-3.1.0/python/shiori_sdk/memory/build.py +135 -0
  49. shiori_sdk-3.1.0/python/shiori_sdk/memory/committed.py +35 -0
  50. shiori_sdk-3.1.0/python/shiori_sdk/memory/context.py +33 -0
  51. shiori_sdk-3.1.0/python/shiori_sdk/memory/engine.py +327 -0
  52. shiori_sdk-3.1.0/python/shiori_sdk/memory/events.py +86 -0
  53. shiori_sdk-3.1.0/python/shiori_sdk/memory/requests.py +55 -0
  54. shiori_sdk-3.1.0/python/shiori_sdk/memory/utils.py +25 -0
  55. shiori_sdk-3.1.0/python/shiori_sdk/messages.py +68 -0
  56. shiori_sdk-3.1.0/python/shiori_sdk/models.py +75 -0
  57. shiori_sdk-3.1.0/python/shiori_sdk/plugin_services.py +69 -0
  58. shiori_sdk-3.1.0/python/shiori_sdk/processes.py +67 -0
  59. shiori_sdk-3.1.0/python/shiori_sdk/prompting.py +26 -0
  60. shiori_sdk-3.1.0/python/shiori_sdk/py.typed +0 -0
  61. shiori_sdk-3.1.0/python/shiori_sdk/redaction.py +70 -0
  62. shiori_sdk-3.1.0/python/shiori_sdk/role_events.py +35 -0
  63. shiori_sdk-3.1.0/python/shiori_sdk/roles.py +68 -0
  64. shiori_sdk-3.1.0/python/shiori_sdk/rpc.py +51 -0
  65. shiori_sdk-3.1.0/python/shiori_sdk/runtime.py +138 -0
  66. shiori_sdk-3.1.0/python/shiori_sdk/sessions.py +64 -0
  67. shiori_sdk-3.1.0/python/shiori_sdk/sql.py +29 -0
  68. shiori_sdk-3.1.0/python/shiori_sdk/storage.py +38 -0
  69. shiori_sdk-3.1.0/python/shiori_sdk/testing/__init__.py +5 -0
  70. shiori_sdk-3.1.0/python/shiori_sdk/testing/accounts.py +180 -0
  71. shiori_sdk-3.1.0/python/shiori_sdk/testing/avatars.py +50 -0
  72. shiori_sdk-3.1.0/python/shiori_sdk/testing/bridge.py +33 -0
  73. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_context.py +151 -0
  74. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_group.py +68 -0
  75. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_hub.py +152 -0
  76. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_intake.py +112 -0
  77. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_services.py +153 -0
  78. shiori_sdk-3.1.0/python/shiori_sdk/testing/channel_sessions.py +76 -0
  79. shiori_sdk-3.1.0/python/shiori_sdk/testing/commands.py +52 -0
  80. shiori_sdk-3.1.0/python/shiori_sdk/testing/context.py +88 -0
  81. shiori_sdk-3.1.0/python/shiori_sdk/testing/diagnostics.py +98 -0
  82. shiori_sdk-3.1.0/python/shiori_sdk/testing/events.py +88 -0
  83. shiori_sdk-3.1.0/python/shiori_sdk/testing/extensions.py +143 -0
  84. shiori_sdk-3.1.0/python/shiori_sdk/testing/hooks.py +59 -0
  85. shiori_sdk-3.1.0/python/shiori_sdk/testing/http.py +88 -0
  86. shiori_sdk-3.1.0/python/shiori_sdk/testing/lifecycle.py +33 -0
  87. shiori_sdk-3.1.0/python/shiori_sdk/testing/memory.py +77 -0
  88. shiori_sdk-3.1.0/python/shiori_sdk/testing/memory_context.py +81 -0
  89. shiori_sdk-3.1.0/python/shiori_sdk/testing/models.py +76 -0
  90. shiori_sdk-3.1.0/python/shiori_sdk/testing/packages.py +61 -0
  91. shiori_sdk-3.1.0/python/shiori_sdk/testing/processes.py +160 -0
  92. shiori_sdk-3.1.0/python/shiori_sdk/testing/pytest_fixtures.py +75 -0
  93. shiori_sdk-3.1.0/python/shiori_sdk/testing/pytest_plugin.py +25 -0
  94. shiori_sdk-3.1.0/python/shiori_sdk/testing/resources.py +14 -0
  95. shiori_sdk-3.1.0/python/shiori_sdk/testing/roles.py +119 -0
  96. shiori_sdk-3.1.0/python/shiori_sdk/testing/runtime.py +15 -0
  97. shiori_sdk-3.1.0/python/shiori_sdk/testing/scene_observations.py +15 -0
  98. shiori_sdk-3.1.0/python/shiori_sdk/testing/service_context.py +59 -0
  99. shiori_sdk-3.1.0/python/shiori_sdk/testing/sessions.py +54 -0
  100. shiori_sdk-3.1.0/python/shiori_sdk/testing/ssl_context.py +68 -0
  101. shiori_sdk-3.1.0/python/shiori_sdk/testing/storage.py +20 -0
  102. shiori_sdk-3.1.0/python/shiori_sdk/testing/tools.py +38 -0
  103. shiori_sdk-3.1.0/python/shiori_sdk/tool_chain.py +22 -0
  104. shiori_sdk-3.1.0/python/shiori_sdk/tool_hooks.py +59 -0
  105. shiori_sdk-3.1.0/python/shiori_sdk/tools.py +162 -0
  106. shiori_sdk-3.1.0/python/shiori_sdk/values.py +13 -0
  107. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/PKG-INFO +384 -0
  108. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/SOURCES.txt +119 -0
  109. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/dependency_links.txt +1 -0
  110. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/entry_points.txt +2 -0
  111. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/requires.txt +8 -0
  112. shiori_sdk-3.1.0/python/shiori_sdk.egg-info/top_level.txt +1 -0
  113. shiori_sdk-3.1.0/setup.cfg +4 -0
  114. shiori_sdk-3.1.0/tests/test_errors.py +41 -0
  115. shiori_sdk-3.1.0/tests/test_event_binding.py +35 -0
  116. shiori_sdk-3.1.0/tests/test_lifecycle.py +24 -0
  117. shiori_sdk-3.1.0/tests/test_media.py +21 -0
  118. shiori_sdk-3.1.0/tests/test_redaction.py +143 -0
  119. shiori_sdk-3.1.0/tests/test_sql.py +47 -0
  120. shiori_sdk-3.1.0/tests/test_storage.py +32 -0
  121. shiori_sdk-3.1.0/tests/test_tools.py +89 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YinFengWindy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,384 @@
1
+ Metadata-Version: 2.4
2
+ Name: shiori-sdk
3
+ Version: 3.1.0
4
+ Summary: Public Shiori plugin contracts and independent testing support.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/YinFengWindy/Shiori-Agent/tree/main/packages/sdk
7
+ Project-URL: Repository, https://github.com/YinFengWindy/Shiori-Agent
8
+ Project-URL: Issues, https://github.com/YinFengWindy/Shiori-Agent/issues
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.28.1
13
+ Requires-Dist: json-repair>=0.40.0
14
+ Provides-Extra: testing
15
+ Requires-Dist: pytest>=9.0; extra == "testing"
16
+ Requires-Dist: pytest-asyncio>=1.3; extra == "testing"
17
+ Requires-Dist: httpx>=0.28; extra == "testing"
18
+ Requires-Dist: PyYAML>=6.0; extra == "testing"
19
+ Dynamic: license-file
20
+
21
+ # Shiori SDK
22
+
23
+ [@yinfengwindy/shiori-sdk](https://www.npmjs.com/package/@yinfengwindy/shiori-sdk)
24
+ and [shiori-sdk](https://pypi.org/project/shiori-sdk/) are the TypeScript and Python distributions of the
25
+ same plugin contract. Both are version **3.1.0**, with Runtime API **3.1.0**.
26
+
27
+ Start with the [plugin tutorial](https://github.com/YinFengWindy/Shiori-Agent/blob/main/docs/_handbook/plugins-tutorial.md)
28
+ and [runtime contract](https://github.com/YinFengWindy/Shiori-Agent/blob/main/docs/_handbook/plugin-runtime-contract.md)
29
+ for plugin layout, capability declarations and packaging.
30
+
31
+ ## Compatibility
32
+
33
+ External plugin manifests declare `runtime_api: ">=3.0.0 <4.0.0"`, or
34
+ `">=3.1.0 <4.0.0"` when they use the 3.1 lifecycle additions (`AfterTurnCtx`,
35
+ `PHASE_SLOTS`, `require_phase_slot`, `LifecycleModule.requires` / `produces`). The host rejects
36
+ an incompatible range with an `incompatible_runtime` diagnostic before executing
37
+ the plugin backend. Version 3 requires rebuilding existing renderer imports.
38
+
39
+ Before the first public release, the npm package moved to the personal scope
40
+ `@yinfengwindy/shiori-sdk`. SDK and Runtime API stay at `3.1.0`; renderer plugins
41
+ built against the earlier internal package name must update their imports and
42
+ rebuild. The host provides only the public package name, without an old-name alias.
43
+
44
+ ## TypeScript
45
+
46
+ Install the SDK and its React peers as development dependencies in your plugin project:
47
+
48
+ ```sh
49
+ pnpm add -D "@yinfengwindy/shiori-sdk@^3.1.0" "react@^19.2.5" "react-dom@^19.2.5"
50
+ ```
51
+
52
+ Build the plugin UI as ESM, externalizing `@yinfengwindy/shiori-sdk`, `react`,
53
+ `react/jsx-runtime`, `react-dom` and `react-dom/client`. Shiori's import map supplies
54
+ the host instances at runtime; do not bundle a separate SDK or React instance.
55
+ The npm package requires React and React DOM `^19.2.5` for local development.
56
+ The host renderer ABI guarantees `19.2.0`; declare compatible host peers separately
57
+ in the plugin's `manifest.yaml`, alongside its Runtime API range:
58
+
59
+ ```yaml
60
+ runtime_api: ">=3.1.0 <4.0.0"
61
+ peer_dependencies:
62
+ react: ">=19.2.0 <20.0.0"
63
+ react-dom: ">=19.2.0 <20.0.0"
64
+ ```
65
+
66
+ The SDK itself is versioned by `runtime_api` and needs no `peer_dependencies` entry.
67
+
68
+ The main entry exports plugin contracts, components and helpers. `/contract`
69
+ contains React/DOM-free types; `/host-internal` is reserved for host code; `/testing`
70
+ provides independent UI fakes and a DOM harness and is never a runtime peer.
71
+ TypeScript consumers of `/testing` use `@types/node >=26.6.3`, declared as an
72
+ optional type peer because happy-dom exposes Web Streams types from that version.
73
+ This is a declaration requirement, not a change to the runtime Node requirement.
74
+ Workspace consumers resolve source; `pnpm --filter @yinfengwindy/shiori-sdk pack` builds an ESM
75
+ tarball with declarations and external React peers. The host import map provides
76
+ the same main entry to precompiled plugins. Only the main entry belongs in a
77
+ plugin's production peer imports.
78
+
79
+ ## Python
80
+
81
+ Requires Python **3.12+**. Install in your plugin project:
82
+
83
+ ```sh
84
+ uv add "shiori-sdk>=3.1.0,<4"
85
+ ```
86
+
87
+ For independent plugin tests, add the optional testing support and run your suite:
88
+
89
+ ```sh
90
+ uv add --dev "shiori-sdk[testing]>=3.1.0,<4"
91
+ uv run pytest tests
92
+ ```
93
+
94
+ These commands set up your plugin's development environment. The Shiori host
95
+ supplies the SDK and declared host dependencies when it runs the installed plugin.
96
+
97
+ The wheel contains contracts and pure values, without a dependency on the host.
98
+ The core lifecycle surface includes `PluginRuntimeContext`,
99
+ `LifecycleFrame`, `LifecycleModule`, `LifecycleCapability`, `EventsCapability`,
100
+ `Dispose`, `EventHandler`, `AfterStepCtx`, `AfterReasoningCtx` and `ResponseMetadata`.
101
+ The host owns phase execution, storage, capability authorization and cleanup.
102
+ `setup(ctx)` receives its declared capabilities; undeclared access raises
103
+ `CapabilityNotGranted`. A declared capability whose backing host service is
104
+ missing fails before `setup` with `HostServiceUnavailable`, naming the service,
105
+ so declared services such as `workspace` or `session_manager` are never `None`.
106
+ Runtime injection is checked through a static
107
+ `PluginSetupContext` without the legacy context's dynamic attribute fallback.
108
+ Typed setup contexts also cover memory, hooks, commands, diagnostics, role/model
109
+ and process services, channels and accounts. Their capability protocols and
110
+ independent testing fixtures are documented below; host service implementations
111
+ remain in the host.
112
+
113
+ ```python
114
+ from shiori_sdk import PluginRuntimeContext
115
+ from shiori_sdk.lifecycle import LifecycleFrame
116
+
117
+ class TraceStep:
118
+ slot = "example.trace"
119
+ requires = ("after_step.copy_input", "step:ctx")
120
+ produces = ("step:telemetry:example",)
121
+
122
+ async def run[FrameT: LifecycleFrame](self, frame: FrameT) -> FrameT:
123
+ frame.slots["step:telemetry:example"] = True
124
+ return frame
125
+
126
+ async def setup(ctx: PluginRuntimeContext) -> None:
127
+ ctx.lifecycle.contribute("after_step", [TraceStep()])
128
+ ```
129
+
130
+ Install `shiori-sdk[testing]` for pytest/pytest-asyncio, `sdk_context`,
131
+ `FakePluginContext`, `FakeLifecycle`, `FakeEvents`, `FakeFrame`, package staging,
132
+ bridge request helpers and shared test TLS contexts. Fakes store only test values
133
+ in memory; they never open host persistence. `sdk_context` reads the nearest
134
+ `manifest.yaml` above the test file (within the pytest rootdir; override the
135
+ `sdk_plugin_dir` fixture otherwise) and grants only its declared `capabilities`,
136
+ validated against the host's `KNOWN_CAPABILITIES`, so undeclared access raises
137
+ `CapabilityNotGranted` as in the host. The manifest is only read: `plugin_dir`
138
+ is an isolated temporary directory, and bundled files are package assets. `FakeLifecycle`
139
+ records modules for explicit execution and rejects slots outside
140
+ `shiori_sdk.lifecycle.PHASE_SLOTS` like the host; it does not simulate the host
141
+ phase dependency sorter.
142
+ HTTP response contracts and tolerant JSON helpers declare httpx/json-repair as
143
+ runtime dependencies. pytest-asyncio and test fixtures remain optional.
144
+ The base wheel can coexist with pytest without the testing extra: unrelated tests
145
+ collect and run normally. Requesting `sdk_context` without the extra reports the
146
+ installation requirement. Optional fixtures load only when their dependencies exist.
147
+ SDK-only tests never start `AppRuntime`. Real host integration fixtures live in
148
+ `shiori_host_testing`, including its real workspace-backed memory fake. That
149
+ private development package is never installed in plugin or SDK isolation.
150
+
151
+ ## Maintenance and validation
152
+
153
+ Release tags `sdk-v<version>` publish the validated npm tarball and Python wheel/sdist.
154
+ See the [publishing guide](https://github.com/YinFengWindy/Shiori-Agent/blob/main/docs/agents/sdk-publishing.md)
155
+ for registry setup, first publication and recovery.
156
+
157
+ `python/shiori_sdk/_version.py` is the version source. Python packaging reads it
158
+ directly. After changing it, run `node scripts/sync_sdk_version.mjs` from the root
159
+ to update npm metadata, then update consumer constraints and locks. Lint and the
160
+ npm build both run the consistency check.
161
+
162
+ ```sh
163
+ pnpm lint
164
+ pnpm typecheck
165
+ pnpm run sdk:smoke
166
+ uv run python -m scripts.verify_sdk
167
+ uv run python -m scripts.verify_plugin_tests
168
+ uv run python -m scripts.check_sdk_imports
169
+ ```
170
+
171
+ The artifact probes install tarball/wheel non-editably outside the checkout. The
172
+ wheel probe first collects/runs an unrelated test with only the base SDK and pytest,
173
+ checks the missing-extra diagnostic, then installs the extra and runs all SDK tests.
174
+ The plugin probe discovers all plugin suites (currently all 20 baseline plugins),
175
+ builds ordinary wheels from external copies, and installs only each target's
176
+ declared dependency closure. Every `shiori-*` package comes from the local
177
+ wheelhouse with `--no-index --no-deps`; a missing local wheel fails instead of
178
+ falling back to an index. Third-party requirements are installed separately and
179
+ `uv pip check` verifies the result. No host or default-memory package is injected.
180
+ Each suite and its awaited failure probe owns a separate pytest temporary directory.
181
+ Provenance checks run before and after the suite, checking host absence, all
182
+ distribution origins, editable installs, repository path injection and SDK versions.
183
+ An execution probe starts before initial conftests and rejects implementation code
184
+ from every staged target/dependency tree or the original checkout (including SDK
185
+ sources), even when a temporary module alias is removed before the suite ends.
186
+ Installed entry origins and hashes cover the target's complete declared plugin closure.
187
+
188
+ The import guard has no exemptions. It checks SDK and plugin Python sources,
189
+ tests, stubs and packaged testing helpers, including TYPE_CHECKING, import aliases,
190
+ literal/string-composed dynamic imports, string patch targets, `importlib.resources`
191
+ and `pkgutil` resource access (attribute, alias or literal `getattr` forms), and host
192
+ resource paths built from `__file__` (`Path`/`pathlib.Path`, `os.path.dirname`/
193
+ `split`/`join`, literal `*[...]` arguments, `os.pardir`, `p /= ...`, folded string
194
+ concatenation) or explicitly rooted at the working directory (`Path.cwd()`,
195
+ `os.getcwd()`, `Path()`, including `os.getcwd() + "/apps/backend"`) that names the host
196
+ layout. A relative path counts as cwd-rooted only when it reaches a file-system sink
197
+ listed in `scripts/sdk_path_sinks.py` (`open`, `io.open`, `os.listdir`/`chdir`/...,
198
+ `glob`, `shutil.*`, `sys.path.insert`/`append`, file-system methods of concrete
199
+ pathlib paths). Host layout (one definition in `scripts/sdk_repository_layout.py`) means a path or
200
+ literal starting with `apps/backend`, `apps/desktop` or `tests/backend` (rejected
201
+ anywhere; URLs, prose and plugin-internal `.../tests/backend/...` are not), an
202
+ existing entry below `apps/backend/`, or a file inside an existing host package
203
+ directory. Ordinary call arguments, `PurePath` values, `str()` and `posixpath.join`
204
+ are not file-system paths; `os.path` functions are recognized through imports only.
205
+ Each violation counts once, at the value where it first appears; paths derived
206
+ from it are the same violation. Path values are followed through names bound in the
207
+ same module only; paths passed through attributes, containers, call results or loops
208
+ (for example `self.root.parents[3]`, `parents[-1]`, `__spec__.origin`,
209
+ `sys.path[0]`, repeated `parent` in a loop) are left to external execution.
210
+ Exclusions are judged relative to the scanned package: virtual environments,
211
+ caches and a plugin's root-level `build/`/`dist/` outputs; package-internal
212
+ directories such as `backend/build/` are scanned wherever the checkout lives.
213
+ Host roots follow the actual backend package/module tree.
214
+ SDK dependencies cannot point to concrete plugins. Declared public sibling plugin
215
+ dependencies remain valid. This static guard is complemented by external execution;
216
+ it does not claim to sandbox arbitrary Python.
217
+
218
+ CI separates all-plugin isolation, SDK artifact tests, host integration and installed
219
+ host-resource checks. Existing renderer and Windows process lifecycle jobs remain.
220
+
221
+
222
+
223
+ ## Memory engines
224
+
225
+ `shiori_sdk.memory.engine` owns memory requests, results, scopes, tool profiles and
226
+ `MemoryEngine`. `memory.events` and `memory.committed` own shared ingestion,
227
+ consolidation and committed-turn values; implementations and SQLite stay outside SDK.
228
+ Pure semantic pagination/filter declarations live in `memory.requests`; each engine
229
+ owns its status vocabulary and removes private vector/hash fields from responses.
230
+
231
+ A memory package exposes `MemoryPlugin.build(MemoryPluginBuildDeps)`,
232
+ `ensure_workspace_storage(...)` and `validate_transition(...)` from its
233
+ `backend/memory_plugin.py`. Build inputs contain resolved `MemoryBuildConfig`
234
+ (model selection and embedding credentials), a `ModelProvider`, bounded HTTP
235
+ requester, typed queued events, a skill-name callback and narrow role/storage ports.
236
+ No full host Config, RoleStore, provider implementation or Markdown service crosses
237
+ this boundary. The storage port retains host migration receipts, atomic publication
238
+ and live-database leases. Compatibility failures use
239
+ `MemoryStorageIncompatibleError.to_details()` for the settings recovery hint.
240
+
241
+ Construction must register every allocation with `deps.resources.register(value,
242
+ cleanup)` before allocating the next resource. Call `transfer()` only after the
243
+ engine is complete and return its records in `MemoryPluginRuntime.resources`;
244
+ this is the only ownership handoff, and the runtime has no separate list of
245
+ closeable objects. Memory databases are opened only through
246
+ `deps.storage.open_database(path)` so the host's live-database lease applies.
247
+ The caller retains an outer construction cleanup scope until all host assembly
248
+ succeeds, then consumes those exact callbacks at shutdown (including opaque values
249
+ with no close method). Returned callbacks run once; cleanup continues in reverse
250
+ order if one callback fails. A direct build caller supplies and owns that scope.
251
+
252
+ Setup plugins declaring `memory` and `rpc` use `MemoryPluginContext`: shared
253
+ Markdown reads, role existence/permissions, storage and current engine are injected,
254
+ while RPC registration remains plugin-owned. `BeforeTurnObservation` exposes only
255
+ inspection fields; `AfterToolResultCtx` is the shared result event. Standalone tests
256
+ can use `FakeMemoryPluginContext`, `FakeMemoryRoles`, `FakeMemoryStorage` and
257
+ `FakeBuildResources`; real migration and runtime assembly remain host integration tests.
258
+
259
+
260
+ ## Tool policies, commands and observation
261
+
262
+ `shiori_sdk.tools` owns `Tool`, `ToolResult` and result normalization; `tool_hooks`
263
+ owns `PreToolCtx`, `HookOutcome` and registration. Policies remain in plugins.
264
+ `HookOutcome(finalize=True, decision="deny")` requests host summarization while
265
+ ordinary denial leaves the existing execution flow intact. `HookPluginContext`
266
+ provides granted config, workspace and hook registration.
267
+
268
+ Before-turn command modules accept a `CommandFrame`, read its immutable
269
+ `CommandInput`, and call `frame.abort_command(reply)`. The host constructs the
270
+ ordinary abort result, retaining channel, timestamp and context scope and skipping
271
+ retrieval/model calls. `last_consolidated` is memory progress, not model compaction.
272
+ `SessionUndo` supplies an atomic undo result and invokes a memory source resolver
273
+ before deleting messages. The optional `MemoryUndo` extension supports dry-run and
274
+ real cleanup. A command resolves it from the current memory engine; engines lacking
275
+ that extension keep working without memory undo. `CommandPluginContext` also
276
+ provides command-menu registration and current declared dependency exports.
277
+
278
+ Observe uses `ObservePluginContext` with `background`, `storage` and `diagnostics`.
279
+ The migration port is shared with memory construction. The host owns receipt/lease
280
+ handling, the active error session, installed package and external plugin code roots,
281
+ and the process-global hook stack. The plugin owns fingerprinting, deduplication,
282
+ flush and SQLite. Register collector cleanup before installing global hooks; stop
283
+ subscriptions before final flush, then cancel retention and writer in reverse order.
284
+ Source checkout depth is never a plugin contract.
285
+
286
+ Status commands declare `observe` as an optional manifest dependency and resolve
287
+ its public `recent_cache_turns` export for each command. Unload yields an unavailable
288
+ reply; reload uses the new export. Consumers do not inspect Observe's database.
289
+
290
+ `testing.extensions.FakeExtensionContext`, `testing.hooks.FakeToolHooks`,
291
+ `testing.commands.FakeCommandFrame` / `FakeSessionUndo` and
292
+ `testing.diagnostics.FakeDiagnostics` provide independent contract doubles.
293
+ Diagnostics fakes record callbacks without modifying process-wide handlers. Global
294
+ hook restoration and real lifecycle ordering stay in host integration tests.
295
+
296
+
297
+ `shiori_sdk.storage.plugin_data_dir` and `PLUGIN_DATA_DIRNAME` own the pure canonical
298
+ private-data layout and portable plugin-ID validation; host migration and SDK fakes
299
+ use the same helper. `PrivateStorage.migrate_data` returns the authoritative target.
300
+ Observe passes that resolved database path to both writer and public telemetry reader,
301
+ so an injected storage root remains consistent and reads never create storage.
302
+
303
+ ## Role, generation and native services
304
+
305
+ `plugin_services.ServicePluginContext` describes statically checked setup inputs.
306
+ Properties require the corresponding manifest grant; the context does not grant
307
+ access merely because the interface declares a property.
308
+
309
+ | Capability | Contract and ownership |
310
+ | --- | --- |
311
+ | `roles` | Detached role snapshots, explicit asset resolution/adoption and opaque role-extension transactions. The host keeps its canonical RoleStore and write lock. |
312
+ | `models` | `async with ctx.models.activate(role_id, "chat" or "vision")` holds the host-selected provider/model snapshot across awaited work. No runtime registry or full Config is exported. |
313
+ | `sessions` | Session metadata, original media provenance, atomic image replacement and its host-owned desktop projection. |
314
+ | `http` | `HttpClient` uses the injected external transport and its default retry/budget policy. Memory's bounded `HttpRequester` remains separate. |
315
+ | `background` | `spawn` owns scoped tasks; `spawn_runtime` additionally retains the calling runtime generation until task completion. Both cancel and join outstanding work on unload. |
316
+ | `processes` | Creates explicit MCP sessions and owned child processes through the host's existing McpClient/owned_spawn/WindowsJob implementations. The SDK contains no process implementation. |
317
+ | `resources` | Supplies source/frozen resource roots and shared emoji paths; plugins do not guess checkout depth. |
318
+ | `tool_turn` | Returns the current host turn identity and awaited ownership finalizers. Model arguments cannot forge it. |
319
+ | `runtime` | `was_active` and `on_drain` preserve accepted work during generation replacement. |
320
+
321
+ `Roles.read_scope()` keeps a plugin's multi-read namespace reconciliation atomic
322
+ against canonical role edits. Plugins retain their schemas and policies; only
323
+ host storage operations cross this boundary. `PrivateStorage.migrate_data`
324
+ returns the authoritative directory and is mandatory when constructing plugin
325
+ catalogs. Neither plugins nor SDK fakes recreate the host's migration owner.
326
+
327
+ Shared prompt-section, scene-observation, RPC-error and MCP values have one SDK
328
+ definition. `files`, `media`, `errors` and `redaction` contain standalone helpers
329
+ operating on explicit values/paths. Native discovery, HTTP transport, role/session
330
+ storage and runtime leases remain host-owned.
331
+
332
+ `testing.service_context.FakeServiceContext` composes independent role, session,
333
+ tool, HTTP, resource, process, RPC and task fixtures. Native calls fail until a
334
+ test explicitly supplies their result. Plugin policy tests execute with only the
335
+ SDK and declared sibling dependencies (Story → NovelAI; Meme → Citation).
336
+ Individual fixtures live in their owning `testing.tools`, `testing.storage`,
337
+ `testing.sessions`, `testing.resources`, `testing.models`, `testing.http`,
338
+ `testing.runtime` and `testing.scene_observations` modules; the context only
339
+ assembles them. Role draft writers and covariant read-only projectors are defined
340
+ once in `shiori_sdk.roles` and used by both the host and independent fixtures.
341
+ Actual kernel ordering, role saves, session media adoption, runtime lease
342
+ retention, screen/desktop-pet integration and Windows Job cleanup remain in host
343
+ tests. PR CI includes a dedicated Windows process-lifecycle job.
344
+
345
+ Desktop pet also consumes `roles`, `storage`, `tools`, `rpc` and typed
346
+ `role_events.RoleDeleted` through this context. Sprite packages, role selection,
347
+ exclusive visibility and asset reconciliation remain plugin policies. Host
348
+ integration tests exercise canonical role locks, atomic saves, migration receipts
349
+ and kernel replacement with those public capabilities. `rpc.register` supports
350
+ `admission_exempt=True` for bounded controls that must remain reachable while
351
+ ordinary admission pauses; desktop-pet bubble dismissal uses this flag.
352
+ Pure timestamp/path helpers are defined once in `shiori_sdk.values`.
353
+
354
+ ## Channel and account contracts
355
+
356
+ Channel plugins use `shiori_sdk.channels.context.ChannelPluginContext`; transport startup receives `shiori_sdk.channels.ChannelContext`. Account values/rules/targets live in `shiori_sdk.accounts`, message values in `shiori_sdk.messages`, stream events in `shiori_sdk.channel_events`, and provenance/quote helpers in `shiori_sdk.channels.message_source` and `reply_context`. The host injects intake, routing, attachment storage, HTTP and avatar services; it retains lifecycle and cache policy.
357
+
358
+ Declare `processes` for native children. `Processes.popen` is the synchronous counterpart of `spawn`, returning a process and an optional `ProcessOwner`; only the host implements OS ownership. QQ keeps NapCat installation, private profiles, QR codes and OneBot policy. Independent testing uses shared `FakeAccounts`, `FakeChannelPluginContext`, `FakeChannelIntake`, `FakeHttp`, `FakeAvatars` and `FakeProcesses`, never host services.
359
+
360
+ `ChannelsCapability.group(name)` constructs the host account-group coordinator; `ChannelSessions.identity_index(...)` constructs the host metadata-backed identity view. Platform dedupe, credential/authentication, stream formatting and reconnect policy stay in plugins. Use `FakeAccountChannelGroup` and `FakeChannelSessions` for independent tests. Scoped KV supports idempotent `delete`; `read_mapping` validates object-valued records before consumers access them. Telegram tool-call preview events and `EventBinding` are shared SDK values.
361
+
362
+
363
+ ## Surface interaction
364
+
365
+ A background owner declares `surfaces.setInteraction(surfaceId, { roleId, available })`
366
+ (or `null` to revoke). The host derives admission from that declaration plus its
367
+ existing visibility, readiness and live window identity; private retained state
368
+ and plugin KV never participate. Hidden, destroyed or reloading windows cannot
369
+ admit speech. An unchanged declaration does not reset an active turn.
370
+
371
+ Every `SurfaceHandle` receives `voice.gesture("press" | "move" | "release" | "cancel")`,
372
+ `voice.onState(listener)` and `onRoleActivity(listener)`. Voice remains host-owned;
373
+ an accepted press pins the role before any awaited recording or ASR. Pointer
374
+ input is attributed by window identity. A global hotkey uses the active available
375
+ owner, otherwise the first available surface in creation order. Another surface
376
+ cannot steal a busy turn. Activity contains only role/session identity, phase and
377
+ notification intent; `null` resets activity on a target change. Plugins own animation
378
+ priority and timing. Reload/crash revokes readiness and stops native drag timers.
379
+
380
+ `createFakeSurfaceHandle(overrides)` in `/testing` supplies a complete independent
381
+ fixture. `pnpm typecheck:plugins` checks every plugin entry and colocated test in a
382
+ standalone TypeScript program with declared workspace dependencies and no host
383
+ ambient declarations. Boundary tests reject both global bridge calls and host
384
+ ambient type references.