orc-hestrator-server 2.0.0__py3-none-any.whl
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.
- orc_hestrator_server-2.0.0.dist-info/METADATA +302 -0
- orc_hestrator_server-2.0.0.dist-info/RECORD +43 -0
- orc_hestrator_server-2.0.0.dist-info/WHEEL +4 -0
- orc_hestrator_server-2.0.0.dist-info/entry_points.txt +9 -0
- orc_hestrator_server-2.0.0.dist-info/licenses/LICENSE +21 -0
- task_orchestrator/__init__.py +3 -0
- task_orchestrator/auth.py +92 -0
- task_orchestrator/checkpoints.py +63 -0
- task_orchestrator/db.py +324 -0
- task_orchestrator/engine/__init__.py +188 -0
- task_orchestrator/engine/archive.py +74 -0
- task_orchestrator/engine/base.py +163 -0
- task_orchestrator/engine/claims.py +189 -0
- task_orchestrator/engine/dependencies.py +195 -0
- task_orchestrator/engine/items.py +391 -0
- task_orchestrator/engine/metrics.py +296 -0
- task_orchestrator/engine/notes.py +75 -0
- task_orchestrator/engine/portability.py +224 -0
- task_orchestrator/engine/presence.py +145 -0
- task_orchestrator/engine/queries.py +233 -0
- task_orchestrator/engine/transitions.py +292 -0
- task_orchestrator/engine/trees.py +106 -0
- task_orchestrator/engine/users.py +334 -0
- task_orchestrator/engine/workspace_view.py +122 -0
- task_orchestrator/onboarding.py +476 -0
- task_orchestrator/prompts.py +146 -0
- task_orchestrator/schemas.py +184 -0
- task_orchestrator/server.py +1135 -0
- task_orchestrator/ui/__init__.py +1 -0
- task_orchestrator/ui/app.py +567 -0
- task_orchestrator/ui/templates/arc_detail.html +179 -0
- task_orchestrator/ui/templates/arcs.html +56 -0
- task_orchestrator/ui/templates/base.html +18 -0
- task_orchestrator/ui/templates/board.html +186 -0
- task_orchestrator/ui/templates/partials/card.html +16 -0
- task_orchestrator/ui/templates/partials/column.html +8 -0
- task_orchestrator/ui/templates/partials/item_detail.html +41 -0
- task_orchestrator/ui/templates/partials/tree_node.html +41 -0
- task_orchestrator/ui/templates/timeline.html +161 -0
- task_orchestrator/web/__init__.py +4 -0
- task_orchestrator/web/api.py +410 -0
- task_orchestrator/web/graph.py +109 -0
- task_orchestrator/workspace.py +111 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: orc-hestrator-server
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: MCP server for AI agent work management — persistent work item graph for AI agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/filhocf/orc-hestrator
|
|
6
|
+
Project-URL: Repository, https://github.com/filhocf/orc-hestrator
|
|
7
|
+
Author: Claudio Ferreira Filho
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,mcp,task-management,workflow
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: croniter>=2.0.0
|
|
18
|
+
Requires-Dist: mcp<2,>=1.0.0
|
|
19
|
+
Requires-Dist: pyyaml>=6.0
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
23
|
+
Provides-Extra: ui
|
|
24
|
+
Requires-Dist: fastapi>=0.115.0; extra == 'ui'
|
|
25
|
+
Requires-Dist: jinja2>=3.1.0; extra == 'ui'
|
|
26
|
+
Requires-Dist: python-multipart>=0.0.9; extra == 'ui'
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'ui'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="docs/assets/orc-logo.png" alt="Orc[hestrator]" width="260">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<h1 align="center">Orc[hestrator]</h1>
|
|
35
|
+
|
|
36
|
+
<p align="center"><strong>The shared work board for humans and AI agents.</strong><br>
|
|
37
|
+
One source of truth for what is being worked on, by whom, right now — enforced by the server,
|
|
38
|
+
not by the operator's discipline.</p>
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/orc-hestrator-server/)
|
|
41
|
+
[](https://www.python.org/downloads/)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](https://github.com/filhocf/orc-hestrator/actions/workflows/ci.yml)
|
|
44
|
+
|
|
45
|
+
Orc[hestrator] is an MCP server that keeps the work of a team of AI agents and their human
|
|
46
|
+
in one place. Agents read and write it over MCP; the human reads and intervenes through a web
|
|
47
|
+
UI. The state lives in a single SQLite database on one host, so switching machines, restarting
|
|
48
|
+
a session, or running three agents in parallel does not fork reality.
|
|
49
|
+
|
|
50
|
+
Distribution: `orc-hestrator-server` on PyPI, commands `orc`, `orc-mcp`, `orc-serve` and `orc-ui`, site [orc-hestrator.dev](https://orc-hestrator.dev).
|
|
51
|
+
Formerly published as `task-orchestrator-py`.
|
|
52
|
+
|
|
53
|
+
## The problem
|
|
54
|
+
|
|
55
|
+
An AI agent forgets. Context compacts, the session ends, you open a different machine, and the
|
|
56
|
+
agent no longer knows what was done, what is blocked, or what comes next. The usual answers do
|
|
57
|
+
not survive contact with real work:
|
|
58
|
+
|
|
59
|
+
- **Prompt-based reminders** disappear with the context window.
|
|
60
|
+
- **A markdown or JSON task file** has no rules: nothing stops an agent from marking work done
|
|
61
|
+
that never ran, and two agents will happily pick the same task.
|
|
62
|
+
- **A hosted issue tracker** is built for humans typing tickets. Its API can be called by an
|
|
63
|
+
agent, but nothing in it is designed for agents as first-class actors.
|
|
64
|
+
|
|
65
|
+
So you end up being the router: retyping state, reconciling boards, and noticing by accident
|
|
66
|
+
that two machines implemented the same feature.
|
|
67
|
+
|
|
68
|
+
## What Orc does
|
|
69
|
+
|
|
70
|
+
- **Server-enforced workflow.** Items move `queue → work → review → done` through explicit
|
|
71
|
+
triggers. The server rejects an invalid transition; the client cannot skip a step.
|
|
72
|
+
- **Dependencies that actually block.** Advancement waits for prerequisites, with cycle
|
|
73
|
+
detection and a configurable `unblock_at` threshold (unblock at review, not only at done).
|
|
74
|
+
- **Gates as required notes.** A YAML schema declares which notes a phase requires. Missing
|
|
75
|
+
documentation blocks the transition, so "done" means something.
|
|
76
|
+
- **Presence and claims across machines.** Every session registers as a triple
|
|
77
|
+
`hostname / agent_id / session_id`, heartbeats, and is reaped when it dies. Claims answer
|
|
78
|
+
"who is on this item or file right now" before two agents duplicate the work.
|
|
79
|
+
- **Attribution per request.** `agent_id` is resolved per call
|
|
80
|
+
(argument → `X-Agent-ID` header → `MCP_AGENT_ID` → OAuth client id) and recorded on writes,
|
|
81
|
+
so the board can say who did what.
|
|
82
|
+
- **One database, many clients.** Run it locally over stdio, or as a central HTTP service that
|
|
83
|
+
every machine and every agent points at.
|
|
84
|
+
|
|
85
|
+
## Who it is for
|
|
86
|
+
|
|
87
|
+
- **A developer running agent CLIs on more than one machine.** Continuity is the whole point:
|
|
88
|
+
the task you started on the laptop is the task the desktop agent picks up.
|
|
89
|
+
- **A small team putting agents into the loop.** Two to ten people, several agents, and a need
|
|
90
|
+
to see who is doing what without a daily sync to rebuild the picture.
|
|
91
|
+
- **Anyone who tried a task file and watched it drift.** If you have ever found two divergent
|
|
92
|
+
copies of the same plan, this is the fix.
|
|
93
|
+
|
|
94
|
+
It is not a runtime orchestrator: Orc does not spawn or supervise agent processes. It is not a
|
|
95
|
+
memory system either: memory stores facts, Orc stores the structure of work. They compose.
|
|
96
|
+
|
|
97
|
+
## Quick start
|
|
98
|
+
|
|
99
|
+
### Single machine, stdio
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
uvx orc-hestrator-server
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Register it with any MCP client:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"orc": {
|
|
111
|
+
"command": "uvx",
|
|
112
|
+
"args": ["orc-hestrator-server"],
|
|
113
|
+
"env": { "ORC_DB": "/path/to/tasks.db" }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Without `ORC_DB` the database defaults to `~/.orc/tasks.db`. The previous name,
|
|
120
|
+
`TASK_ORCHESTRATOR_DB`, still works as a fallback with a deprecation notice.
|
|
121
|
+
|
|
122
|
+
### One server, many machines
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# on the host that owns the database
|
|
126
|
+
MCP_HOST=127.0.0.1 MCP_PORT=8005 \
|
|
127
|
+
ORC_DB=~/.local/share/orc/tasks.db \
|
|
128
|
+
uvx --from 'orc-hestrator-server[ui]' orc-serve
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Then point every client at `http://<host>:8005/mcp`. The web UI is served from the same app
|
|
132
|
+
(`/ui/`), and the REST API from `/api`. Put a TLS proxy in front: the UI and API use cookie
|
|
133
|
+
sessions, and an initial admin is seeded from `ORC_ADMIN_USER` / `ORC_ADMIN_PASSWORD` on first
|
|
134
|
+
boot when no user exists.
|
|
135
|
+
|
|
136
|
+
The `[ui]` extra carries `fastapi` + `uvicorn`, which the HTTP transport needs; a plain install
|
|
137
|
+
does not pull it in — hence `--from 'orc-hestrator-server[ui]'`. From a checkout the equivalent
|
|
138
|
+
is `uv sync --extra ui` then `uv run orc-serve`.
|
|
139
|
+
|
|
140
|
+
## Screens
|
|
141
|
+
|
|
142
|
+
The management UI is a React app (Vite, TypeScript, Tailwind) with nine screens: login,
|
|
143
|
+
overview, live team presence, kanban board, arc tree, dependency graph, task detail,
|
|
144
|
+
users/RBAC, and profile.
|
|
145
|
+
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
The screen that matters most is **live team presence**: which host, which agent, which session,
|
|
149
|
+
on which item, right now.
|
|
150
|
+
|
|
151
|
+
## How it compares
|
|
152
|
+
|
|
153
|
+
| | Hosted issue trackers | Agent memory servers | Task file in the repo | Orc |
|
|
154
|
+
|---|---|---|---|---|
|
|
155
|
+
| Designed for agents as primary actors | No | Yes, for facts | No | Yes |
|
|
156
|
+
| Workflow enforced by the server | Partly, configurable | Not applicable | No | Yes |
|
|
157
|
+
| Blocks advancement on unmet dependencies | Sometimes | No | No | Yes |
|
|
158
|
+
| Cross-machine single source of truth | Yes | Usually per host | No, it forks | Yes |
|
|
159
|
+
| Presence and claims ("who is on this") | Human assignee only | No | No | Yes, with heartbeat |
|
|
160
|
+
| Gates that require evidence | Paid tiers, human-driven | No | No | Yes |
|
|
161
|
+
| Runs on your own box, one SQLite file | No | Yes | Yes | Yes |
|
|
162
|
+
|
|
163
|
+
## The tools you will actually use
|
|
164
|
+
|
|
165
|
+
Thirty-one tools exist; a normal session touches eight of them.
|
|
166
|
+
|
|
167
|
+
| Tool | What it is for |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `get_context` | Dashboard of the current state, or the detail of one item |
|
|
170
|
+
| `get_next_item` | Highest-priority item with no unsatisfied dependency |
|
|
171
|
+
| `manage_items` | Create, update, delete; batch and hierarchy |
|
|
172
|
+
| `create_work_tree` | Root plus children plus dependency wiring in one call |
|
|
173
|
+
| `advance_item` | Move an item through a trigger, with enforcement |
|
|
174
|
+
| `manage_notes` | Phase notes, including the ones gates require |
|
|
175
|
+
| `agent_register` / `agent_heartbeat` / `agent_end` | Presence lifecycle |
|
|
176
|
+
| `claim` / `release` / `conflicts` | Take and free an item or a free-form resource |
|
|
177
|
+
|
|
178
|
+
Full reference: [docs/TOOLS.md](docs/TOOLS.md). Seven MCP prompts ship with the server
|
|
179
|
+
(`session_start`, `work_summary`, `quick_start`, `create_item_from_context`,
|
|
180
|
+
`status_progression`, `dependency_manager`, `batch_complete`).
|
|
181
|
+
|
|
182
|
+
## Workflow
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
start start start
|
|
186
|
+
queue ────────→ work ────────→ review ────────→ done
|
|
187
|
+
│ │ │
|
|
188
|
+
│ block │ block │ block
|
|
189
|
+
└──────→ blocked ←──────────────┘
|
|
190
|
+
│
|
|
191
|
+
│ resume
|
|
192
|
+
└──────→ (previous status)
|
|
193
|
+
|
|
194
|
+
done/cancelled ──reopen──→ queue
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Triggers: `start`, `complete`, `block`/`hold`, `resume`, `cancel`, `reopen`.
|
|
198
|
+
|
|
199
|
+
Enforcement: dependencies must be satisfied before an item leaves `queue`; note-gate schemas are
|
|
200
|
+
checked on every `start` and `complete`; reopening cascades to the parent when the parent is
|
|
201
|
+
terminal.
|
|
202
|
+
|
|
203
|
+
## Example
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
create_work_tree(
|
|
207
|
+
root_title="User authentication",
|
|
208
|
+
children_json='[
|
|
209
|
+
{"ref": "schema", "title": "Design auth schema", "priority": "high"},
|
|
210
|
+
{"ref": "api", "title": "Implement auth endpoints", "priority": "high"},
|
|
211
|
+
{"ref": "tests", "title": "Write auth tests", "priority": "medium"}
|
|
212
|
+
]',
|
|
213
|
+
deps_json='[
|
|
214
|
+
{"from": "schema", "to": "api"},
|
|
215
|
+
{"from": "api", "to": "tests"}
|
|
216
|
+
]'
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
agent_register(cli="hermes") # presence: host/agent/session
|
|
220
|
+
get_next_item() # -> "Design auth schema"
|
|
221
|
+
claim(subject_type="work_item", subject_ref="a1b2", session_id="...")
|
|
222
|
+
advance_item(item_id="a1b2", trigger="start")
|
|
223
|
+
manage_notes(operation="upsert", item_id="a1b2", key="requirements", role="queue",
|
|
224
|
+
body="RS256, refresh tokens, 15 min access expiry")
|
|
225
|
+
advance_item(item_id="a1b2", trigger="complete")
|
|
226
|
+
# -> unblocked_items: ["Implement auth endpoints"]
|
|
227
|
+
agent_end(session_id="...") # releases the claim
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Architecture
|
|
231
|
+
|
|
232
|
+
A thin MCP layer over a modular domain engine, SQLite with WAL, plus an optional web layer.
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
src/task_orchestrator/
|
|
236
|
+
├── server.py MCP tools (FastMCP) and per-request agent identity
|
|
237
|
+
├── engine/ domain logic, one module per concern:
|
|
238
|
+
│ ├── base, items, transitions, dependencies, notes, queries, trees
|
|
239
|
+
│ ├── metrics, archive, portability, workspace_view
|
|
240
|
+
│ └── presence, claims, users coordination and access
|
|
241
|
+
├── db.py schema, additive migrations, FTS5
|
|
242
|
+
├── schemas.py note-gate schemas
|
|
243
|
+
├── workspace.py workspace configs
|
|
244
|
+
├── web/ REST API (/api) and graph endpoints
|
|
245
|
+
└── ui/ server-rendered board (FastAPI + HTMX)
|
|
246
|
+
frontend/ React management UI (isolated from the Python tree)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Modules form a strict import DAG and `engine/` re-exports its public surface, so clients keep a
|
|
250
|
+
stable `engine.X` interface. Design decisions are recorded in [docs/adr/](docs/adr/):
|
|
251
|
+
central service, session registry incorporation, single-source-of-truth before hybrid,
|
|
252
|
+
edge security, per-request agent identity.
|
|
253
|
+
|
|
254
|
+
## Security
|
|
255
|
+
|
|
256
|
+
- `/api/*` requires an authenticated user session except login, logout and `/health`.
|
|
257
|
+
- Passwords use scrypt with a per-user salt; sessions are opaque tokens in an HTTP-only cookie
|
|
258
|
+
with server-side revocation; roles are `admin` and `user`.
|
|
259
|
+
- MCP clients can add a second barrier: OAuth2 resource-server mode (`MCP_OAUTH_ENABLED`) verifies
|
|
260
|
+
opaque pre-shared tokens, behind the reverse proxy's basic auth.
|
|
261
|
+
- The admin user is seeded from `ORC_ADMIN_USER` / `ORC_ADMIN_PASSWORD` on first boot.
|
|
262
|
+
|
|
263
|
+
## Roadmap
|
|
264
|
+
|
|
265
|
+
Shipped through 1.5.0: workflow engine, dependency graph with cycle detection, note-gate
|
|
266
|
+
schemas, workspaces, metrics and graph analytics, scheduling, archive and checkpoints,
|
|
267
|
+
export/import, central presence and claims, per-request attribution, REST API, user auth with
|
|
268
|
+
RBAC, and the React management UI.
|
|
269
|
+
|
|
270
|
+
Next, in rough order:
|
|
271
|
+
|
|
272
|
+
| Area | Focus |
|
|
273
|
+
|---|---|
|
|
274
|
+
| Event ingestion | Git, CI and webhooks move items instead of a human remembering to |
|
|
275
|
+
| Typed gates | Blocking predicates: CI status, coverage, approver role, no bypass |
|
|
276
|
+
| Work-item types | First-class incident (`detect → mitigate → resolve → postmortem`) and spike flows |
|
|
277
|
+
| Claim leases | TTL on claims so a dead agent does not hold an item forever |
|
|
278
|
+
| Authoring UI | Create and edit items from the browser, not only from MCP |
|
|
279
|
+
| Hybrid mode | Local primary with a central secondary, for offline work |
|
|
280
|
+
|
|
281
|
+
## Origins
|
|
282
|
+
|
|
283
|
+
Orc started as a Python port of [jpicklyk/task-orchestrator](https://github.com/jpicklyk/task-orchestrator)
|
|
284
|
+
(Kotlin). It has since diverged: tags, batch operations, work trees, short hex ids, stale
|
|
285
|
+
detection, structured errors, graph analytics, central presence, claims, attribution, and the
|
|
286
|
+
web layer are Python-only. The Kotlin project deserves credit for the original design.
|
|
287
|
+
|
|
288
|
+
## Contributing
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
git clone https://github.com/filhocf/orc-hestrator
|
|
292
|
+
cd orc-hestrator
|
|
293
|
+
uv sync --extra test --extra ui
|
|
294
|
+
uv run pytest tests/ -q
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Changes go to a branch and a PR against `main`. Product requirements live in
|
|
298
|
+
[docs/PRD.md](docs/PRD.md); architecture in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
task_orchestrator/__init__.py,sha256=jPs0aALQshSz4KFQCcu2aTl0J4GurPh3s4J-52P36zM,95
|
|
2
|
+
task_orchestrator/auth.py,sha256=e1Q9CaZ8pFx4wD1i2mcGpf9xiMxFB-hkvHOTxNt7gmo,2967
|
|
3
|
+
task_orchestrator/checkpoints.py,sha256=dq1NaMbyomWeBAgEOlSspaU-m5XB7WHU77Eu5gfoegY,1981
|
|
4
|
+
task_orchestrator/db.py,sha256=8E2Xm9PoD7lAgDnGrjuYX02nY19lYYKt4j6y2XFzGKQ,12219
|
|
5
|
+
task_orchestrator/onboarding.py,sha256=p3Om-aCvGHEwmfeb8YBiw6lmxDyo-RHpmKtnnbx9k3A,19671
|
|
6
|
+
task_orchestrator/prompts.py,sha256=H5kAM4S-UWhUaKeQ7AjdmNqez-jGuMIVLlTWgtfJV-g,5983
|
|
7
|
+
task_orchestrator/schemas.py,sha256=w9v3MIAQ9dVHsxjvULXP6Qcf6ppOutJzTLvfoloDBow,6199
|
|
8
|
+
task_orchestrator/server.py,sha256=o3RNMOKBtgv0KZl5jBWXf0Qaj35bHzD8TLNqdWTjiFE,39790
|
|
9
|
+
task_orchestrator/workspace.py,sha256=Rv8CpN9N9i3rgvKCd7BpKB_Dxr9L-Igek_5-LdtbiIg,2937
|
|
10
|
+
task_orchestrator/engine/__init__.py,sha256=3dESUZCZJKK_EOQrHIiQtMuesaOAP2wDEA-Q1sDKD3g,4156
|
|
11
|
+
task_orchestrator/engine/archive.py,sha256=gk0-YHA-gE2yzotQ_yaiZhsv-meRXsfUa2x0tlDYSGY,2648
|
|
12
|
+
task_orchestrator/engine/base.py,sha256=hoqqiQjNwoincD40XQBfuboiQMBCsA7z17BCli6gAfs,4914
|
|
13
|
+
task_orchestrator/engine/claims.py,sha256=7h6HQWjoQ0LBW4SEGdtaewWg01F1lhZZ25GsSatr3e4,6749
|
|
14
|
+
task_orchestrator/engine/dependencies.py,sha256=E3q1Xm-n_v5lbvFtuWuocwbt1-dl6Of5v_J1f6lEu-A,7059
|
|
15
|
+
task_orchestrator/engine/items.py,sha256=P22fr4Y1ZNT0jvJfRJAlA3dUrXyznMByogCmBwCP_RA,12930
|
|
16
|
+
task_orchestrator/engine/metrics.py,sha256=uxmMVmIBo0r-4bXDGbhG7699xnJ_Jx5n6pJ8aK-cxa8,11588
|
|
17
|
+
task_orchestrator/engine/notes.py,sha256=sQdpjw_rMtI5sICyEk69Chrwm12v_aD-F65cqTwcyCY,2706
|
|
18
|
+
task_orchestrator/engine/portability.py,sha256=nE52VCxH-vRmXSOQht7wo7RujnDpGFbMHEw2wfqFm-I,8169
|
|
19
|
+
task_orchestrator/engine/presence.py,sha256=ZldTXqOD2U7wh1WOH3HTLp6Dwk1oFHt_kEtZnYScyEY,4995
|
|
20
|
+
task_orchestrator/engine/queries.py,sha256=KuT-dgm0dVreYRjA0sKfZ3Ape3j9dpVKwwbDpRLnCIA,8693
|
|
21
|
+
task_orchestrator/engine/transitions.py,sha256=OXCkxRV1GxO_1mO6Nqa_-LcdFioGO0xVCuR8J4ortt4,10927
|
|
22
|
+
task_orchestrator/engine/trees.py,sha256=6uF_QgNHKtig15NeYTlOShcBEPwoa6CPOK8ZJ5PR_fI,3526
|
|
23
|
+
task_orchestrator/engine/users.py,sha256=aufkO2uWhAgwFnsqUfrS5n8CQ8WXl0B0OnXqVowJn_0,11227
|
|
24
|
+
task_orchestrator/engine/workspace_view.py,sha256=v8mO38dEWFfKJxUvxdNuHz8sOCGD5Ctj2YLkq_SkpZ0,4557
|
|
25
|
+
task_orchestrator/ui/__init__.py,sha256=-_32roXmM7or5RlP-MPfTlT51608bsDMlUJavBeJmPU,43
|
|
26
|
+
task_orchestrator/ui/app.py,sha256=kPT7PTbQY9CwMXCSJD-YVrWO943_pL-rj0in0aeoEB8,18943
|
|
27
|
+
task_orchestrator/ui/templates/arc_detail.html,sha256=AIxwcRObQnKi1aaDxBlDV-mcoGWrZvFN4F_7ynQA_KE,8232
|
|
28
|
+
task_orchestrator/ui/templates/arcs.html,sha256=nCV4dyehcSFvN3lrECarTTx1f3JDoCsIK5_8JnORl90,2631
|
|
29
|
+
task_orchestrator/ui/templates/base.html,sha256=QC9lWoM5tO1t7w36nlfgBrFvksgFYuC-yjxoSodc5No,657
|
|
30
|
+
task_orchestrator/ui/templates/board.html,sha256=yK4ut5CzFrxWuYX4GVJH4-JpWEPHzM2-3E9mZNEjOE0,9013
|
|
31
|
+
task_orchestrator/ui/templates/timeline.html,sha256=8-RFcMCzwCvN34KDX12AGi4CVSUIJsD9W8gd0ZjtjGw,6085
|
|
32
|
+
task_orchestrator/ui/templates/partials/card.html,sha256=koHu3nP44x32Z45LH5TIVymWUeg71lgPtLGFcCBg5qo,801
|
|
33
|
+
task_orchestrator/ui/templates/partials/column.html,sha256=-8mA1dZRJl1E8K3UyQFklNmi2HG8BDGpYtDMZCdzgdQ,300
|
|
34
|
+
task_orchestrator/ui/templates/partials/item_detail.html,sha256=kai5Vgy2z6blqks14WOgEDy1A_qVkP5I_S9ByeEMMX4,1727
|
|
35
|
+
task_orchestrator/ui/templates/partials/tree_node.html,sha256=HikdrkpvjecjBx3oCmgEfWwgDgn-rbRUlXKrDryA6Fw,2160
|
|
36
|
+
task_orchestrator/web/__init__.py,sha256=fmXEU1h-eE8A9uhJorfHAF9oS8t2bBhisQTVzkipuXQ,178
|
|
37
|
+
task_orchestrator/web/api.py,sha256=0Onpmewsq3Yj93Nkxyj1UGzO2-UtrEcJI2bEAwRftDw,16039
|
|
38
|
+
task_orchestrator/web/graph.py,sha256=VVEx1uxKVeGmQo__doykthI0l5HkQY7xhRNhfxl7HoM,3981
|
|
39
|
+
orc_hestrator_server-2.0.0.dist-info/METADATA,sha256=NbDSZXc4eSc3DuZ2NN2l7HKqgxnWLVr1xIl23LE2IcU,13387
|
|
40
|
+
orc_hestrator_server-2.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
41
|
+
orc_hestrator_server-2.0.0.dist-info/entry_points.txt,sha256=hUC7tIDTOCkKwfnop5qCKrtyz3nvPYYkoPwVDBeIfPs,399
|
|
42
|
+
orc_hestrator_server-2.0.0.dist-info/licenses/LICENSE,sha256=zYn23Qf0BnHSrScYg5N_5UKnPZ_3niJaq5eGAE7TKXo,1079
|
|
43
|
+
orc_hestrator_server-2.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
orc = task_orchestrator.server:main
|
|
3
|
+
orc-hestrator = task_orchestrator.server:main
|
|
4
|
+
orc-hestrator-server = task_orchestrator.server:main
|
|
5
|
+
orc-mcp = task_orchestrator.server:main
|
|
6
|
+
orc-serve = task_orchestrator.server:main_http
|
|
7
|
+
orc-ui = task_orchestrator.ui.app:main
|
|
8
|
+
task-orchestrator-py = task_orchestrator.server:main_legacy
|
|
9
|
+
task-orchestrator-ui = task_orchestrator.ui.app:main_legacy
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Claudio Ferreira Filho
|
|
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,92 @@
|
|
|
1
|
+
"""OAuth2 resource-server authentication with opaque pre-shared tokens.
|
|
2
|
+
|
|
3
|
+
Implements TokenVerifier interface for MCP FastMCP auth.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import hmac
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from mcp.server.auth.provider import AccessToken, TokenVerifier
|
|
10
|
+
from mcp.server.auth.settings import AuthSettings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OpaqueTokenVerifier:
|
|
14
|
+
"""Token verifier for opaque pre-shared tokens."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, persona_secrets: dict[str, str] | None = None) -> None:
|
|
17
|
+
"""Initialize with persona->secret mapping.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
persona_secrets: Dict of persona names to their secrets.
|
|
21
|
+
If None, will read from MCP_OAUTH_TOKENS env var.
|
|
22
|
+
"""
|
|
23
|
+
if persona_secrets is None:
|
|
24
|
+
self.persona_secrets = self._load_tokens_from_env()
|
|
25
|
+
else:
|
|
26
|
+
self.persona_secrets = persona_secrets
|
|
27
|
+
|
|
28
|
+
def _load_tokens_from_env(self) -> dict[str, str]:
|
|
29
|
+
"""Load persona->secret mapping from MCP_OAUTH_TOKENS environment variable.
|
|
30
|
+
|
|
31
|
+
Expected format: "persona1:secret1,persona2:secret2"
|
|
32
|
+
"""
|
|
33
|
+
tokens_env = os.environ.get("MCP_OAUTH_TOKENS", "")
|
|
34
|
+
if not tokens_env:
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
result = {}
|
|
38
|
+
for pair in tokens_env.split(","):
|
|
39
|
+
if ":" not in pair:
|
|
40
|
+
continue
|
|
41
|
+
persona, secret = pair.split(":", 1)
|
|
42
|
+
result[persona.strip()] = secret.strip()
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
async def verify_token(self, token: str) -> AccessToken | None:
|
|
46
|
+
"""Verify bearer token against configured secrets.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
token: Bearer token to verify
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
AccessToken if valid, None if invalid/empty
|
|
53
|
+
"""
|
|
54
|
+
if not token:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
# Check against all configured secrets using constant-time comparison
|
|
58
|
+
for persona, secret in self.persona_secrets.items():
|
|
59
|
+
if hmac.compare_digest(token, secret):
|
|
60
|
+
return AccessToken(
|
|
61
|
+
token=token, client_id=persona, scopes=["orc"], expires_at=None
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_auth() -> tuple[TokenVerifier | None, AuthSettings | None]:
|
|
68
|
+
"""Build auth configuration from environment variables.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Tuple of (TokenVerifier, AuthSettings) if OAuth is enabled,
|
|
72
|
+
(None, None) if disabled.
|
|
73
|
+
"""
|
|
74
|
+
# Check if OAuth is enabled
|
|
75
|
+
oauth_enabled = os.environ.get("MCP_OAUTH_ENABLED", "").lower()
|
|
76
|
+
if oauth_enabled not in ("true", "1", "yes"):
|
|
77
|
+
return (None, None)
|
|
78
|
+
|
|
79
|
+
# Create token verifier
|
|
80
|
+
verifier = OpaqueTokenVerifier()
|
|
81
|
+
|
|
82
|
+
# Build AuthSettings with required fields
|
|
83
|
+
issuer_url = os.environ.get("MCP_OAUTH_ISSUER", "https://cfnarede.dev/orc")
|
|
84
|
+
resource_server_url = os.environ.get("MCP_OAUTH_RESOURCE_SERVER_URL", issuer_url)
|
|
85
|
+
|
|
86
|
+
auth_settings = AuthSettings(
|
|
87
|
+
issuer_url=issuer_url,
|
|
88
|
+
resource_server_url=resource_server_url,
|
|
89
|
+
required_scopes=["orc"],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return (verifier, auth_settings)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Checkpoint management — export snapshots and DB integrity verification."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sqlite3
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import db as _db
|
|
10
|
+
from . import engine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _default_output_dir() -> str:
|
|
14
|
+
"""Default checkpoint dir: same directory as the SQLite DB file."""
|
|
15
|
+
return str(Path(_db.DB_PATH).parent)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_checkpoint(output_dir: str | None = None) -> str:
|
|
19
|
+
"""Export graph and save to checkpoint-{ISO-timestamp}.json. Returns file path."""
|
|
20
|
+
out = output_dir or _default_output_dir()
|
|
21
|
+
os.makedirs(out, exist_ok=True)
|
|
22
|
+
data = engine.export_graph()
|
|
23
|
+
ts = datetime.now(UTC).strftime("%Y%m%dT%H%M%S%fZ")
|
|
24
|
+
filename = f"checkpoint-{ts}.json"
|
|
25
|
+
filepath = os.path.join(out, filename)
|
|
26
|
+
with open(filepath, "w") as f:
|
|
27
|
+
json.dump(data, f, default=str)
|
|
28
|
+
return filepath
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def list_checkpoints(output_dir: str | None = None) -> list[str]:
|
|
32
|
+
"""List .json checkpoint files in output dir sorted by date (newest first)."""
|
|
33
|
+
out = output_dir or _default_output_dir()
|
|
34
|
+
if not os.path.isdir(out):
|
|
35
|
+
return []
|
|
36
|
+
files = sorted(
|
|
37
|
+
(
|
|
38
|
+
os.path.join(out, name)
|
|
39
|
+
for name in os.listdir(out)
|
|
40
|
+
if name.startswith("checkpoint-") and name.endswith(".json")
|
|
41
|
+
),
|
|
42
|
+
reverse=True,
|
|
43
|
+
)
|
|
44
|
+
return files
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def restore_checkpoint(path: str) -> bool:
|
|
48
|
+
"""Restore graph from a checkpoint file using import_graph(replace). Returns True on success."""
|
|
49
|
+
with open(path) as f:
|
|
50
|
+
data = json.load(f)
|
|
51
|
+
engine.import_graph(data, mode="replace")
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def verify_db_integrity() -> bool:
|
|
56
|
+
"""Run PRAGMA integrity_check on the DB. Returns True if healthy."""
|
|
57
|
+
try:
|
|
58
|
+
conn = sqlite3.connect(_db.DB_PATH)
|
|
59
|
+
result = conn.execute("PRAGMA integrity_check").fetchone()
|
|
60
|
+
conn.close()
|
|
61
|
+
return result[0] == "ok"
|
|
62
|
+
except (sqlite3.DatabaseError, sqlite3.OperationalError):
|
|
63
|
+
return False
|