fable-engine 1.3.7__tar.gz → 1.3.8__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.
- {fable_engine-1.3.7/fable_engine.egg-info → fable_engine-1.3.8}/PKG-INFO +44 -27
- {fable_engine-1.3.7 → fable_engine-1.3.8}/README.md +43 -26
- {fable_engine-1.3.7 → fable_engine-1.3.8}/docs/stop-ai-agents-writing-too-early.md +42 -17
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/base.py +1 -1
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/server.py +1 -1
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/session.py +1 -1
- {fable_engine-1.3.7 → fable_engine-1.3.8/fable_engine.egg-info}/PKG-INFO +44 -27
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine.egg-info/SOURCES.txt +1 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/__init__.py +1 -1
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/launcher.py +56 -2
- {fable_engine-1.3.7 → fable_engine-1.3.8}/pyproject.toml +1 -1
- {fable_engine-1.3.7 → fable_engine-1.3.8}/setup.py +1 -1
- fable_engine-1.3.8/tests/test_shell_call.py +58 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_skill_distribution.py +27 -2
- {fable_engine-1.3.7 → fable_engine-1.3.8}/LICENSE +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/MANIFEST.in +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/docs/ai-evidence-adjudicator.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/docs/fable-v1-v2-migration.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/docs/fable-v2-architecture.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/docs/system3-architecture.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_compressor.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/cas.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/deliberation.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/fleet.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/lifecycle.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/scrapers.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/actions/system3.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/adjudicator.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/browser.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/cas.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/fable_session.json +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/guards.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/schema.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/arxiv.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/github.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/reddit.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/web.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/x.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/scrapers/youtube.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/test_server.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine/updater.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine.egg-info/dependency_links.txt +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine.egg-info/entry_points.txt +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_engine.egg-info/top_level.txt +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/__main__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/adapters.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/installer.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/manifest.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/resources.json +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/safety.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode/skill_bundle.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_mode_entry.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/adapters.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/ast_tools.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/compute.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/design_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/diagnostics.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/mock_auditor.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/mutation.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/property_oracle.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/sandbox_executor.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/test_harness.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/vector_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/visual.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/coder_fleet/workspace.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/cortical/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/cortical/plasticity_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/execution_broker.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/proof_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/protocol.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/runtime.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/causal.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/dialectical.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/evolution.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/executive.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/free_energy.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/hyperbolic.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/induction.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/kripke.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/system3/oracle.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/fable_v2/verifiers.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/rules/AGENTS.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/rules/GEMINI.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/rules/fable-mode.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/setup.cfg +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/SKILL.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/concurrency.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/design_3d.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/frontend_design.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/process.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/python.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/research.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/rust.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/security.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/system.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/cortex/target.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/distributed-system-design.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/agentic-execution.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/cognitive-protocol.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/deepthink-mode.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/design-system.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/innovation-engine.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/interleaved-verification.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/proof-architecture.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/system2-session-engine.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/__init__.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_adjudicator.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_anti_loop_circuit_breaker.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_auto_updater.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_coder_fleet.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_delegation_compiler.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_descriptor_boundaries.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_design_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_epistemic_evidence_validator.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_execution_broker.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_fable_v2.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_fleet_transitions.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_fsm_redteam_evolution.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_goal_rubric_and_pipeline.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_hebbian_plasticity.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_packaging_runtime.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_proof_engine.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_red_team_seal_path.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_red_team_swarm.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_redteam_remediation.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_registration_transaction.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_requested_regressions.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_scrapers.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_server_actions.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_server_frontier_actions.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_server_protocol.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_stealth_browser.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_system3.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_system3_deep_integration.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_system3_frontier.py +0 -0
- {fable_engine-1.3.7 → fable_engine-1.3.8}/tests/test_vector_engine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: fable-engine
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8
|
|
4
4
|
Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
|
|
5
5
|
Author: REX-codebase
|
|
6
6
|
License: MIT License
|
|
@@ -133,56 +133,73 @@ red-team swarm for code, and a sealed record of what was verified.
|
|
|
133
133
|
|
|
134
134
|
<br/>
|
|
135
135
|
|
|
136
|
-
###
|
|
136
|
+
### One package, two agent environments
|
|
137
|
+
|
|
138
|
+
Fable ships as one PyPI package. The same package contains the runtime, stdio
|
|
139
|
+
MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
|
|
140
|
+
server never silently activates workspace instructions.
|
|
141
|
+
|
|
142
|
+
Run setup from the project the agent will work in:
|
|
137
143
|
|
|
138
144
|
```bash
|
|
139
|
-
|
|
145
|
+
uvx --from fable-engine==1.3.8 fable-mode setup --yes
|
|
140
146
|
```
|
|
141
147
|
|
|
142
|
-
|
|
148
|
+
This resolves the pinned package in an isolated uv environment and copies the
|
|
149
|
+
bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
|
|
150
|
+
`--target <dir>` for another skill directory. For a persistent install, use:
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
```bash
|
|
153
|
+
python -m pip install fable-engine
|
|
154
|
+
fable-mode setup --yes
|
|
155
|
+
```
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
Then choose only the invocation that matches the agent environment.
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
#### Native MCP client
|
|
160
|
+
|
|
161
|
+
Run `fable-engine` as the stdio server. For example:
|
|
150
162
|
|
|
151
163
|
```jsonc
|
|
152
|
-
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.
|
|
153
|
-
// Cursor:
|
|
164
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
|
|
165
|
+
// Cursor or another JSON-configured client:
|
|
154
166
|
{
|
|
155
167
|
"mcpServers": {
|
|
156
168
|
"fable-engine": {
|
|
157
169
|
"command": "uvx",
|
|
158
|
-
"args": ["--from", "fable-engine==1.3.
|
|
170
|
+
"args": ["--from", "fable-engine==1.3.8", "fable-engine"]
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
```
|
|
163
175
|
|
|
164
|
-
|
|
176
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
#### Shell sandbox with internet, no MCP host
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
Installing `fable-engine` gives your agent the MCP tools. It does not install
|
|
171
|
-
or activate the Fable Mode Agent Skill - the prompt-side workflow in
|
|
172
|
-
[`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
|
|
173
|
-
activates those instructions on its own; the skill is always a separate,
|
|
174
|
-
explicit opt-in.
|
|
175
|
-
|
|
176
|
-
The complete skill tree ships inside the wheel. To install it into your
|
|
177
|
-
project's skills directory (the cross-client `.agents/skills/` convention):
|
|
180
|
+
The same package exposes a direct JSON transport. Pipe one `fable_session`
|
|
181
|
+
argument object to `fable-mode call`:
|
|
178
182
|
|
|
179
183
|
```bash
|
|
180
|
-
|
|
184
|
+
printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
|
|
185
|
+
| uvx --from fable-engine==1.3.8 fable-mode call
|
|
181
186
|
```
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
The command uses JSON Lines: one `fable_session` argument object per input line
|
|
189
|
+
and one JSON result per output line. Keep that process open for a full workflow so
|
|
190
|
+
the authority timer and session stay in the same trusted runtime. A one-line pipe
|
|
191
|
+
is useful for a single inspection call. Each uvx command can resolve an
|
|
192
|
+
isolated environment; `pip install` is better when the sandbox keeps a Python
|
|
193
|
+
environment between calls. Session data persists outside that environment in
|
|
194
|
+
Fable's data directory (`FABLE_DATA_DIR` can override it).
|
|
195
|
+
|
|
196
|
+
Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
|
|
197
|
+
|
|
198
|
+
#### Skill activation remains explicit
|
|
199
|
+
|
|
200
|
+
`setup` is the unified path. The older `install-skill` command remains as a
|
|
201
|
+
compatible alias for skill-only installation. Neither `fable-engine` nor
|
|
202
|
+
`pip install fable-engine` writes instructions into a workspace on its own.
|
|
186
203
|
|
|
187
204
|
<br/>
|
|
188
205
|
|
|
@@ -90,56 +90,73 @@ red-team swarm for code, and a sealed record of what was verified.
|
|
|
90
90
|
|
|
91
91
|
<br/>
|
|
92
92
|
|
|
93
|
-
###
|
|
93
|
+
### One package, two agent environments
|
|
94
|
+
|
|
95
|
+
Fable ships as one PyPI package. The same package contains the runtime, stdio
|
|
96
|
+
MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
|
|
97
|
+
server never silently activates workspace instructions.
|
|
98
|
+
|
|
99
|
+
Run setup from the project the agent will work in:
|
|
94
100
|
|
|
95
101
|
```bash
|
|
96
|
-
|
|
102
|
+
uvx --from fable-engine==1.3.8 fable-mode setup --yes
|
|
97
103
|
```
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
This resolves the pinned package in an isolated uv environment and copies the
|
|
106
|
+
bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
|
|
107
|
+
`--target <dir>` for another skill directory. For a persistent install, use:
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
```bash
|
|
110
|
+
python -m pip install fable-engine
|
|
111
|
+
fable-mode setup --yes
|
|
112
|
+
```
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
Then choose only the invocation that matches the agent environment.
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
#### Native MCP client
|
|
117
|
+
|
|
118
|
+
Run `fable-engine` as the stdio server. For example:
|
|
107
119
|
|
|
108
120
|
```jsonc
|
|
109
|
-
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.
|
|
110
|
-
// Cursor:
|
|
121
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
|
|
122
|
+
// Cursor or another JSON-configured client:
|
|
111
123
|
{
|
|
112
124
|
"mcpServers": {
|
|
113
125
|
"fable-engine": {
|
|
114
126
|
"command": "uvx",
|
|
115
|
-
"args": ["--from", "fable-engine==1.3.
|
|
127
|
+
"args": ["--from", "fable-engine==1.3.8", "fable-engine"]
|
|
116
128
|
}
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
```
|
|
120
132
|
|
|
121
|
-
|
|
133
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
|
|
122
134
|
|
|
123
|
-
|
|
135
|
+
#### Shell sandbox with internet, no MCP host
|
|
124
136
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Installing `fable-engine` gives your agent the MCP tools. It does not install
|
|
128
|
-
or activate the Fable Mode Agent Skill - the prompt-side workflow in
|
|
129
|
-
[`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
|
|
130
|
-
activates those instructions on its own; the skill is always a separate,
|
|
131
|
-
explicit opt-in.
|
|
132
|
-
|
|
133
|
-
The complete skill tree ships inside the wheel. To install it into your
|
|
134
|
-
project's skills directory (the cross-client `.agents/skills/` convention):
|
|
137
|
+
The same package exposes a direct JSON transport. Pipe one `fable_session`
|
|
138
|
+
argument object to `fable-mode call`:
|
|
135
139
|
|
|
136
140
|
```bash
|
|
137
|
-
|
|
141
|
+
printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
|
|
142
|
+
| uvx --from fable-engine==1.3.8 fable-mode call
|
|
138
143
|
```
|
|
139
144
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
The command uses JSON Lines: one `fable_session` argument object per input line
|
|
146
|
+
and one JSON result per output line. Keep that process open for a full workflow so
|
|
147
|
+
the authority timer and session stay in the same trusted runtime. A one-line pipe
|
|
148
|
+
is useful for a single inspection call. Each uvx command can resolve an
|
|
149
|
+
isolated environment; `pip install` is better when the sandbox keeps a Python
|
|
150
|
+
environment between calls. Session data persists outside that environment in
|
|
151
|
+
Fable's data directory (`FABLE_DATA_DIR` can override it).
|
|
152
|
+
|
|
153
|
+
Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
|
|
154
|
+
|
|
155
|
+
#### Skill activation remains explicit
|
|
156
|
+
|
|
157
|
+
`setup` is the unified path. The older `install-skill` command remains as a
|
|
158
|
+
compatible alias for skill-only installation. Neither `fable-engine` nor
|
|
159
|
+
`pip install fable-engine` writes instructions into a workspace on its own.
|
|
143
160
|
|
|
144
161
|
<br/>
|
|
145
162
|
|
|
@@ -2,27 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
AI coding agents often move from a plausible plan to editing files before they have gathered enough evidence. Fable Mode adds a mechanical session gate: the agent can inspect and reason, but the Fable Engine will not report write permission until the configured authority timer and proof prerequisites pass.
|
|
4
4
|
|
|
5
|
-
Fable Mode
|
|
5
|
+
Fable Mode ships as one package with three parts:
|
|
6
6
|
|
|
7
|
-
- **Fable Engine** is the MCP server that stores session state and enforces its gates.
|
|
8
|
-
- **The Fable Mode Agent Skill** is the
|
|
7
|
+
- **Fable Engine** is the stdio MCP server that stores session state and enforces its gates.
|
|
8
|
+
- **The Fable Mode Agent Skill** is the bundled workflow for compatible agents.
|
|
9
|
+
- **The shell transport** calls the same `fable_session` API when an agent has a shell and internet but no MCP host.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
The package is unified; activation stays explicit. Installing or launching the
|
|
12
|
+
server does not silently write Agent Skill instructions into a project.
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## Set up from one package
|
|
13
15
|
|
|
14
|
-
You need Python 3.10+ and [uv](https://docs.astral.sh/uv/getting-started/installation/).
|
|
16
|
+
You need Python 3.10+ and either [uv](https://docs.astral.sh/uv/getting-started/installation/) or pip.
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
From the project the agent will work in:
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
uvx --from fable-engine==1.3.
|
|
21
|
+
uvx --from fable-engine==1.3.8 fable-mode setup --dry-run
|
|
22
|
+
uvx --from fable-engine==1.3.8 fable-mode setup --yes
|
|
20
23
|
```
|
|
21
24
|
|
|
25
|
+
`setup` copies the complete bundled skill to `.agents/skills/fable-mode`. Use
|
|
26
|
+
`--target <dir>` for another skill directory. The installer refuses to
|
|
27
|
+
overwrite local edits unless you pass `--force`.
|
|
28
|
+
|
|
29
|
+
For a persistent environment, install the same package once:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m pip install fable-engine
|
|
33
|
+
fable-mode setup --yes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Route A: an agent with native MCP access
|
|
37
|
+
|
|
38
|
+
Run `fable-engine` as its stdio MCP server.
|
|
39
|
+
|
|
22
40
|
### Claude Code
|
|
23
41
|
|
|
24
42
|
```bash
|
|
25
|
-
claude mcp add fable-engine -- uvx --from fable-engine==1.3.
|
|
43
|
+
claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
|
|
26
44
|
```
|
|
27
45
|
|
|
28
46
|
### Cursor or another JSON-configured MCP client
|
|
@@ -32,26 +50,33 @@ claude mcp add fable-engine -- uvx --from fable-engine==1.3.7 fable-engine
|
|
|
32
50
|
"mcpServers": {
|
|
33
51
|
"fable-engine": {
|
|
34
52
|
"command": "uvx",
|
|
35
|
-
"args": ["--from", "fable-engine==1.3.
|
|
53
|
+
"args": ["--from", "fable-engine==1.3.8", "fable-engine"]
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
}
|
|
39
57
|
```
|
|
40
58
|
|
|
41
|
-
Restart or reload the client
|
|
59
|
+
Restart or reload the client. Its tool list should include `fable_session`.
|
|
42
60
|
|
|
43
|
-
##
|
|
61
|
+
## Route B: an internet-enabled shell sandbox without MCP access
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
Pipe one `fable_session` argument object to `fable-mode call`:
|
|
46
64
|
|
|
47
65
|
```bash
|
|
48
|
-
|
|
49
|
-
uvx --from fable-engine==1.3.
|
|
66
|
+
printf '%s\n' '{"action":"create_session","session_name":"parser-refactor","objective":"Refactor the parser without changing its public behavior","time_budget_minutes":2}' \
|
|
67
|
+
| uvx --from fable-engine==1.3.8 fable-mode call
|
|
50
68
|
```
|
|
51
69
|
|
|
52
|
-
The
|
|
70
|
+
The command uses JSON Lines and passes each object to the same Fable handler used
|
|
71
|
+
by MCP. It prints one machine-readable result per input line. Keep one process
|
|
72
|
+
open for the full Think -> Prove -> Attack -> Write workflow; restarting the
|
|
73
|
+
process intentionally starts a fresh authority clock. Invalid, empty, non-object,
|
|
74
|
+
or over-1-MiB requests fail with a nonzero exit code and write the error to stderr.
|
|
53
75
|
|
|
54
|
-
|
|
76
|
+
For later calls, send the same argument objects shown below. When repeated uvx
|
|
77
|
+
calls are undesirable, use the persistent pip installation and replace the
|
|
78
|
+
command after the pipe with `fable-mode call`. Session files live outside the
|
|
79
|
+
uvx environment; set `FABLE_DATA_DIR` to choose their location.
|
|
55
80
|
|
|
56
81
|
## Run a first session
|
|
57
82
|
|
|
@@ -25,7 +25,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger("fable-engine.scrapers.base")
|
|
27
27
|
|
|
28
|
-
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.
|
|
28
|
+
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.8"
|
|
29
29
|
|
|
30
30
|
MAX_RETRY_DELAY_SECONDS = 10.0
|
|
31
31
|
|
|
@@ -1567,7 +1567,7 @@ class FableSession:
|
|
|
1567
1567
|
def to_dict(self) -> Dict[str, Any]:
|
|
1568
1568
|
"""Serializes session to dictionary."""
|
|
1569
1569
|
return {
|
|
1570
|
-
"version": "1.3.
|
|
1570
|
+
"version": "1.3.8",
|
|
1571
1571
|
"session_name": self.session_name,
|
|
1572
1572
|
"session_id": self.session_id,
|
|
1573
1573
|
"objective": self.objective,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: fable-engine
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8
|
|
4
4
|
Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
|
|
5
5
|
Author: REX-codebase
|
|
6
6
|
License: MIT License
|
|
@@ -133,56 +133,73 @@ red-team swarm for code, and a sealed record of what was verified.
|
|
|
133
133
|
|
|
134
134
|
<br/>
|
|
135
135
|
|
|
136
|
-
###
|
|
136
|
+
### One package, two agent environments
|
|
137
|
+
|
|
138
|
+
Fable ships as one PyPI package. The same package contains the runtime, stdio
|
|
139
|
+
MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
|
|
140
|
+
server never silently activates workspace instructions.
|
|
141
|
+
|
|
142
|
+
Run setup from the project the agent will work in:
|
|
137
143
|
|
|
138
144
|
```bash
|
|
139
|
-
|
|
145
|
+
uvx --from fable-engine==1.3.8 fable-mode setup --yes
|
|
140
146
|
```
|
|
141
147
|
|
|
142
|
-
|
|
148
|
+
This resolves the pinned package in an isolated uv environment and copies the
|
|
149
|
+
bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
|
|
150
|
+
`--target <dir>` for another skill directory. For a persistent install, use:
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
```bash
|
|
153
|
+
python -m pip install fable-engine
|
|
154
|
+
fable-mode setup --yes
|
|
155
|
+
```
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
Then choose only the invocation that matches the agent environment.
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
#### Native MCP client
|
|
160
|
+
|
|
161
|
+
Run `fable-engine` as the stdio server. For example:
|
|
150
162
|
|
|
151
163
|
```jsonc
|
|
152
|
-
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.
|
|
153
|
-
// Cursor:
|
|
164
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
|
|
165
|
+
// Cursor or another JSON-configured client:
|
|
154
166
|
{
|
|
155
167
|
"mcpServers": {
|
|
156
168
|
"fable-engine": {
|
|
157
169
|
"command": "uvx",
|
|
158
|
-
"args": ["--from", "fable-engine==1.3.
|
|
170
|
+
"args": ["--from", "fable-engine==1.3.8", "fable-engine"]
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
```
|
|
163
175
|
|
|
164
|
-
|
|
176
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
#### Shell sandbox with internet, no MCP host
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
Installing `fable-engine` gives your agent the MCP tools. It does not install
|
|
171
|
-
or activate the Fable Mode Agent Skill - the prompt-side workflow in
|
|
172
|
-
[`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
|
|
173
|
-
activates those instructions on its own; the skill is always a separate,
|
|
174
|
-
explicit opt-in.
|
|
175
|
-
|
|
176
|
-
The complete skill tree ships inside the wheel. To install it into your
|
|
177
|
-
project's skills directory (the cross-client `.agents/skills/` convention):
|
|
180
|
+
The same package exposes a direct JSON transport. Pipe one `fable_session`
|
|
181
|
+
argument object to `fable-mode call`:
|
|
178
182
|
|
|
179
183
|
```bash
|
|
180
|
-
|
|
184
|
+
printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
|
|
185
|
+
| uvx --from fable-engine==1.3.8 fable-mode call
|
|
181
186
|
```
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
The command uses JSON Lines: one `fable_session` argument object per input line
|
|
189
|
+
and one JSON result per output line. Keep that process open for a full workflow so
|
|
190
|
+
the authority timer and session stay in the same trusted runtime. A one-line pipe
|
|
191
|
+
is useful for a single inspection call. Each uvx command can resolve an
|
|
192
|
+
isolated environment; `pip install` is better when the sandbox keeps a Python
|
|
193
|
+
environment between calls. Session data persists outside that environment in
|
|
194
|
+
Fable's data directory (`FABLE_DATA_DIR` can override it).
|
|
195
|
+
|
|
196
|
+
Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
|
|
197
|
+
|
|
198
|
+
#### Skill activation remains explicit
|
|
199
|
+
|
|
200
|
+
`setup` is the unified path. The older `install-skill` command remains as a
|
|
201
|
+
compatible alias for skill-only installation. Neither `fable-engine` nor
|
|
202
|
+
`pip install fable-engine` writes instructions into a workspace on its own.
|
|
186
203
|
|
|
187
204
|
<br/>
|
|
188
205
|
|
|
@@ -154,6 +154,7 @@ tests/test_scrapers.py
|
|
|
154
154
|
tests/test_server_actions.py
|
|
155
155
|
tests/test_server_frontier_actions.py
|
|
156
156
|
tests/test_server_protocol.py
|
|
157
|
+
tests/test_shell_call.py
|
|
157
158
|
tests/test_skill_distribution.py
|
|
158
159
|
tests/test_stealth_browser.py
|
|
159
160
|
tests/test_system3.py
|
|
@@ -249,10 +249,59 @@ def _is_frozen() -> bool:
|
|
|
249
249
|
return bool(getattr(sys, "frozen", False) or getattr(sys, "_MEIPASS", None))
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
MAX_SHELL_REQUEST_BYTES = 1 * 1024 * 1024
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _shell_call() -> int:
|
|
256
|
+
"""Call fable_session over bounded JSON Lines without an MCP host."""
|
|
257
|
+
from fable_engine.server import handle_fable_session
|
|
258
|
+
|
|
259
|
+
# JSON Lines is a UTF-8 protocol. Session text can contain characters the
|
|
260
|
+
# platform default encoding (cp1252 on Windows pipes) cannot encode, so
|
|
261
|
+
# pin both output streams to UTF-8 instead of inheriting the locale.
|
|
262
|
+
for std in (sys.stdout, sys.stderr):
|
|
263
|
+
reconfigure = getattr(std, "reconfigure", None)
|
|
264
|
+
if callable(reconfigure):
|
|
265
|
+
reconfigure(encoding="utf-8", errors="replace")
|
|
266
|
+
|
|
267
|
+
stream = getattr(sys.stdin, "buffer", sys.stdin)
|
|
268
|
+
handled = 0
|
|
269
|
+
while True:
|
|
270
|
+
raw = stream.readline(MAX_SHELL_REQUEST_BYTES + 1)
|
|
271
|
+
if not raw:
|
|
272
|
+
break
|
|
273
|
+
if isinstance(raw, str):
|
|
274
|
+
raw = raw.encode("utf-8")
|
|
275
|
+
if len(raw) > MAX_SHELL_REQUEST_BYTES or (len(raw) == MAX_SHELL_REQUEST_BYTES + 1 and not raw.endswith(b"\n")):
|
|
276
|
+
print("call: request exceeds 1 MiB", file=sys.stderr)
|
|
277
|
+
return 2
|
|
278
|
+
if not raw.strip():
|
|
279
|
+
continue
|
|
280
|
+
try:
|
|
281
|
+
request = json.loads(raw.decode("utf-8"))
|
|
282
|
+
if not isinstance(request, dict):
|
|
283
|
+
raise ValueError("request must be a JSON object")
|
|
284
|
+
if not isinstance(request.get("action"), str) or not request["action"]:
|
|
285
|
+
raise ValueError("request must include a non-empty string action")
|
|
286
|
+
result = handle_fable_session(request)
|
|
287
|
+
except (UnicodeDecodeError, json.JSONDecodeError, ValueError, TypeError, OSError) as exc:
|
|
288
|
+
print(f"call: {exc}", file=sys.stderr)
|
|
289
|
+
return 2
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
print(f"call: {exc}", file=sys.stderr)
|
|
292
|
+
return 1
|
|
293
|
+
print(json.dumps({"ok": True, "result": result}, ensure_ascii=False), flush=True)
|
|
294
|
+
handled += 1
|
|
295
|
+
if not handled:
|
|
296
|
+
print("call: expected at least one JSON object on stdin", file=sys.stderr)
|
|
297
|
+
return 2
|
|
298
|
+
return 0
|
|
299
|
+
|
|
300
|
+
|
|
252
301
|
def _parser() -> argparse.ArgumentParser:
|
|
253
302
|
p = argparse.ArgumentParser(prog="fable-mode")
|
|
254
303
|
p.add_argument("--version", action="version", version=__version__)
|
|
255
|
-
p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall", "install-skill"), default="install")
|
|
304
|
+
p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall", "install-skill", "setup", "call"), default="install")
|
|
256
305
|
p.add_argument("--yes", action="store_true", help="confirm unattended installation")
|
|
257
306
|
p.add_argument("--register-hosts", action="store_true")
|
|
258
307
|
p.add_argument("--aliases", action="store_true", help="also probe legacy cc/antigravity aliases")
|
|
@@ -305,7 +354,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
305
354
|
except (OSError, RuntimeError, ValueError) as exc:
|
|
306
355
|
print(f"serve: {exc}", file=sys.stderr)
|
|
307
356
|
return 1
|
|
308
|
-
if args.mode == "
|
|
357
|
+
if args.mode == "call":
|
|
358
|
+
return _shell_call()
|
|
359
|
+
if args.mode in {"install-skill", "setup"}:
|
|
309
360
|
target = (Path(args.target).expanduser() if args.target
|
|
310
361
|
else Path.cwd() / ".agents" / "skills" / SKILL_NAME)
|
|
311
362
|
if not args.dry_run and not args.yes:
|
|
@@ -329,6 +380,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
329
380
|
print(f"{verb} the Fable Mode Agent Skill ({result.planned} files: {changes}) to {result.target}")
|
|
330
381
|
if not result.dry_run:
|
|
331
382
|
print("Restart or reload your agent to pick up the skill.")
|
|
383
|
+
if args.mode == "setup":
|
|
384
|
+
print("Native MCP: run `fable-engine` as a stdio server.")
|
|
385
|
+
print("Shell sandbox: pipe one fable_session argument object to `fable-mode call`.")
|
|
332
386
|
return 0
|
|
333
387
|
installer = Installer(Path(args.install_dir) if args.install_dir else None)
|
|
334
388
|
if args.mode == "verify":
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fable-engine"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.8"
|
|
8
8
|
description = "Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "REX-codebase" }]
|
|
@@ -40,7 +40,7 @@ class build_py(_build_py):
|
|
|
40
40
|
|
|
41
41
|
setup(
|
|
42
42
|
name="fable-engine",
|
|
43
|
-
version="1.3.
|
|
43
|
+
version="1.3.8",
|
|
44
44
|
description="Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server",
|
|
45
45
|
long_description=README,
|
|
46
46
|
long_description_content_type="text/markdown",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Direct shell transport for agents without native MCP access."""
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from fable_mode.launcher import MAX_SHELL_REQUEST_BYTES, _shell_call
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ShellCallTests(unittest.TestCase):
|
|
12
|
+
def _call(self, payload: bytes):
|
|
13
|
+
stdin = io.TextIOWrapper(io.BytesIO(payload), encoding="utf-8")
|
|
14
|
+
stdout, stderr = io.StringIO(), io.StringIO()
|
|
15
|
+
with mock.patch.object(sys, "stdin", stdin), mock.patch.object(sys, "stdout", stdout), mock.patch.object(sys, "stderr", stderr):
|
|
16
|
+
status = _shell_call()
|
|
17
|
+
return status, stdout.getvalue(), stderr.getvalue()
|
|
18
|
+
|
|
19
|
+
def test_valid_json_lines_share_one_runtime_and_emit_one_result_each(self):
|
|
20
|
+
requests = [{"action": "list_sessions"}, {"action": "list_sessions"}]
|
|
21
|
+
payload = "".join(json.dumps(item) + "\n" for item in requests).encode()
|
|
22
|
+
with mock.patch("fable_engine.server.handle_fable_session", side_effect=["first", "second"]) as handle:
|
|
23
|
+
status, stdout, stderr = self._call(payload)
|
|
24
|
+
self.assertEqual(status, 0)
|
|
25
|
+
self.assertEqual(stderr, "")
|
|
26
|
+
self.assertEqual([json.loads(line) for line in stdout.splitlines()],
|
|
27
|
+
[{"ok": True, "result": "first"}, {"ok": True, "result": "second"}])
|
|
28
|
+
self.assertEqual([call.args[0] for call in handle.call_args_list], requests)
|
|
29
|
+
|
|
30
|
+
def test_empty_invalid_and_non_object_requests_fail(self):
|
|
31
|
+
for payload in (b"", b"not json", b"[]", b'{}', b'{"action": ""}'):
|
|
32
|
+
with self.subTest(payload=payload):
|
|
33
|
+
status, stdout, stderr = self._call(payload)
|
|
34
|
+
self.assertEqual(status, 2)
|
|
35
|
+
self.assertEqual(stdout, "")
|
|
36
|
+
self.assertTrue(stderr.startswith("call: "))
|
|
37
|
+
|
|
38
|
+
def test_oversized_request_is_rejected_before_json_parse(self):
|
|
39
|
+
status, stdout, stderr = self._call(b"{" + b"x" * MAX_SHELL_REQUEST_BYTES)
|
|
40
|
+
self.assertEqual(status, 2)
|
|
41
|
+
self.assertEqual(stdout, "")
|
|
42
|
+
self.assertIn("exceeds 1 MiB", stderr)
|
|
43
|
+
|
|
44
|
+
def test_output_stays_utf8_when_pipe_encoding_is_not(self):
|
|
45
|
+
# Windows pipes default to cp1252: session text with emoji must still
|
|
46
|
+
# reach the caller as decodable UTF-8 JSON instead of crashing.
|
|
47
|
+
buffer = io.BytesIO()
|
|
48
|
+
narrow_stdout = io.TextIOWrapper(buffer, encoding="cp1252")
|
|
49
|
+
stdin = io.TextIOWrapper(io.BytesIO(b'{"action": "list_sessions"}\n'), encoding="utf-8")
|
|
50
|
+
with mock.patch.object(sys, "stdin", stdin), mock.patch.object(sys, "stdout", narrow_stdout), \
|
|
51
|
+
mock.patch("fable_engine.server.handle_fable_session",
|
|
52
|
+
return_value="### \U0001f5c2\ufe0f Available Fable Sessions"):
|
|
53
|
+
status = _shell_call()
|
|
54
|
+
narrow_stdout.flush()
|
|
55
|
+
self.assertEqual(status, 0)
|
|
56
|
+
payload = json.loads(buffer.getvalue().decode("utf-8"))
|
|
57
|
+
self.assertTrue(payload["ok"])
|
|
58
|
+
self.assertIn("\U0001f5c2\ufe0f", payload["result"])
|