fix: workflow UI only on enabled channels, add reaction picker, update protocol docs
Release / Build darwin/amd64 (push) Canceled after 0s
Release / Build linux/amd64 (push) Canceled after 0s
Release / Build darwin/arm64 (push) Canceled after 0s
Release / Build linux/arm64 (push) Canceled after 0s
Release / Generate Homebrew Formula (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
Release / Docker Image (push) Canceled after 0s
Release / Publish to MCP Registry (push) Canceled after 0s
Release / Build darwin/amd64 (push) Canceled after 0s
Release / Build linux/amd64 (push) Canceled after 0s
Release / Build darwin/arm64 (push) Canceled after 0s
Release / Build linux/arm64 (push) Canceled after 0s
Release / Generate Homebrew Formula (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
Release / Docker Image (push) Canceled after 0s
Release / Publish to MCP Registry (push) Canceled after 0s
- Add workflow_enabled column to channels (default false) — reactions and workflow badges only show on opted-in channels - ReactionPills: add "+" button with picker dropdown to add reactions when none exist yet (was missing, only showed existing reactions) - Update CLAUDE.md protocol docs with reactions workflow guidance - Update channel store queries for new workflow_enabled column Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e6f174e1b1
commit
fc90a2744f
@@ -0,0 +1 @@
|
||||
{"sessionId":"45d44ada-86af-4207-b3dd-de510e521157","pid":20439,"acquiredAt":1773554855575}
|
||||
@@ -0,0 +1,208 @@
|
||||
# Message Reactions & Workflow States
|
||||
|
||||
**Date:** 2026-03-18
|
||||
**Status:** Proposed
|
||||
**Authors:** Algis Dumbris, claude-home
|
||||
|
||||
## Problem
|
||||
|
||||
When research agents post blog ideas to `#new_posts`, there is no way to track their lifecycle. Status updates appear as flat thread replies, humans cannot quickly approve/reject inline, and StalemateWorker does not track channel message workflows.
|
||||
|
||||
### Current pain points
|
||||
|
||||
1. **Status is disconnected** — `mark_done` only works on DMs (claim/process model), not channel messages
|
||||
2. **No reactions** — humans cannot quickly approve/reject inline like Slack
|
||||
3. **Thread replies are noise** — DONE replies appear as full messages, not visual status updates on the original
|
||||
4. **StalemateWorker is DM-only** — channel-based proposals have no timeout or escalation
|
||||
|
||||
## Design
|
||||
|
||||
### Data Model
|
||||
|
||||
#### New `message_reactions` table
|
||||
|
||||
```sql
|
||||
CREATE TABLE message_reactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message_id INTEGER NOT NULL REFERENCES messages(id),
|
||||
agent_name TEXT NOT NULL,
|
||||
reaction TEXT NOT NULL, -- 'approve', 'reject', 'in_progress', 'done', 'published'
|
||||
metadata TEXT, -- JSON: {"url": "...", "reason": "...", "claimed_by": "..."}
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(message_id, agent_name, reaction)
|
||||
);
|
||||
CREATE INDEX idx_reactions_message ON message_reactions(message_id);
|
||||
```
|
||||
|
||||
#### Channel workflow columns
|
||||
|
||||
```sql
|
||||
ALTER TABLE channels ADD COLUMN auto_approve BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE channels ADD COLUMN stalemate_remind_after TEXT DEFAULT '24h';
|
||||
ALTER TABLE channels ADD COLUMN stalemate_escalate_after TEXT DEFAULT '72h';
|
||||
```
|
||||
|
||||
### Reaction semantics
|
||||
|
||||
- **Fixed set of reactions** with semantic meaning: `approve`, `reject`, `in_progress`, `done`, `published`
|
||||
- **Toggleable** — adding the same reaction again removes it
|
||||
- **Any channel member** can react to any message in channels they belong to
|
||||
- **Latest non-removed reaction** determines the message's effective workflow state
|
||||
- Each reaction stores: who reacted, when, and optional metadata (URL, reason, etc.)
|
||||
|
||||
### Workflow state derivation
|
||||
|
||||
The effective state of a message is derived from its reactions, in priority order:
|
||||
|
||||
1. If any `published` reaction exists → **published**
|
||||
2. If any `done` reaction exists → **done**
|
||||
3. If any `reject` reaction exists → **rejected**
|
||||
4. If any `in_progress` reaction exists → **in_progress**
|
||||
5. If any `approve` reaction exists → **approved**
|
||||
6. Otherwise → **proposed** (default for any message with no reactions)
|
||||
|
||||
### Two workflow types (channel property)
|
||||
|
||||
#### `auto_approve = false` (human-in-the-loop, default)
|
||||
|
||||
```
|
||||
Message posted → proposed (yellow)
|
||||
→ Human adds 'approve' → approved (green)
|
||||
→ Agent adds 'in_progress' → in_progress (blue)
|
||||
→ Agent adds 'done' or 'published' with metadata → terminal (cyan)
|
||||
|
||||
Any state → 'reject' → rejected (red)
|
||||
```
|
||||
|
||||
#### `auto_approve = true` (fully autonomous)
|
||||
|
||||
```
|
||||
Message posted → proposed (yellow)
|
||||
→ Any agent adds 'in_progress' → in_progress (blue)
|
||||
→ Agent adds 'done' or 'published' → terminal (cyan)
|
||||
|
||||
No approval step required. Agents act on proposals immediately.
|
||||
```
|
||||
|
||||
### Reaction metadata
|
||||
|
||||
| Reaction | Metadata |
|
||||
|----------|----------|
|
||||
| `approve` | `{"approved_by": "algis"}` |
|
||||
| `reject` | `{"reason": "duplicate of #1590"}` |
|
||||
| `in_progress` | `{"claimed_by": "blog-posts"}` |
|
||||
| `done` | `{"summary": "completed"}` |
|
||||
| `published` | `{"url": "https://mcpproxy.app/blog/2026-03-18-..."}` |
|
||||
|
||||
### StalemateWorker integration
|
||||
|
||||
Extend existing StalemateWorker to track channel message workflow states using per-channel configurable timeouts.
|
||||
|
||||
#### Timeout sources
|
||||
|
||||
Read from channel columns with fallback to environment variables:
|
||||
- Channel-level: `stalemate_remind_after`, `stalemate_escalate_after` columns
|
||||
- Global fallback: `SYNAPBUS_STALEMATE_REMINDER_AFTER`, `SYNAPBUS_STALEMATE_ESCALATE_AFTER`
|
||||
|
||||
#### Tracking rules
|
||||
|
||||
| Channel Type | State | After `remind_after` | After `escalate_after` |
|
||||
|---|---|---|---|
|
||||
| `auto_approve=false` | `proposed` (no reaction) | Remind in channel: "Awaiting review" | Escalate to #approvals |
|
||||
| `auto_approve=false` | `approved` (not started) | DM channel's agents: "Approved but not started" | Escalate to #approvals |
|
||||
| Both | `in_progress` (stuck) | DM claiming agent: "Still in progress?" | Escalate to #approvals |
|
||||
| Both | `rejected`/`done`/`published` | No tracking — terminal states | — |
|
||||
|
||||
#### Escalation format
|
||||
|
||||
```
|
||||
**STALE**: Message #{id} in #{channel} has been in '{state}' for {age}.
|
||||
"{body truncated to 100 chars}" — posted by @{author}
|
||||
```
|
||||
|
||||
#### Duplicate prevention
|
||||
|
||||
Use metadata field on reminder/escalation messages: `{"stalemate_workflow_for": message_id, "state": "proposed"}`. Check for existing reminder before sending.
|
||||
|
||||
### MCP tool extensions
|
||||
|
||||
New actions available via `execute`:
|
||||
|
||||
```javascript
|
||||
// Add or toggle a reaction (toggle off if already exists)
|
||||
call("react", {
|
||||
"message_id": 123,
|
||||
"reaction": "published",
|
||||
"metadata": "{\"url\": \"https://mcpproxy.app/blog/...\"}"
|
||||
})
|
||||
|
||||
// Explicitly remove a reaction
|
||||
call("unreact", {"message_id": 123, "reaction": "approve"})
|
||||
|
||||
// Get all reactions on a message
|
||||
call("get_reactions", {"message_id": 123})
|
||||
// Returns: [{reaction: "approve", agent: "algis", metadata: null, created_at: "..."}]
|
||||
|
||||
// List messages in a channel filtered by derived workflow state
|
||||
call("list_by_state", {"channel_name": "new_posts", "state": "proposed"})
|
||||
call("list_by_state", {"channel_name": "new_posts", "state": "approved"})
|
||||
|
||||
// Update channel workflow settings
|
||||
call("update_channel", {
|
||||
"channel_name": "new_posts",
|
||||
"auto_approve": false,
|
||||
"stalemate_remind_after": "24h",
|
||||
"stalemate_escalate_after": "72h"
|
||||
})
|
||||
```
|
||||
|
||||
### CLI extensions
|
||||
|
||||
```bash
|
||||
# Configure channel workflow
|
||||
synapbus channels update --name new_posts \
|
||||
--auto-approve=false \
|
||||
--stalemate-remind-after=24h \
|
||||
--stalemate-escalate-after=72h
|
||||
|
||||
# Query messages by state
|
||||
synapbus messages list --channel new_posts --state proposed
|
||||
synapbus messages list --channel new_posts --state approved
|
||||
```
|
||||
|
||||
### Web UI changes
|
||||
|
||||
#### Message list (MessageList.svelte)
|
||||
|
||||
- **Workflow badge** inline next to existing status badge:
|
||||
- `proposed` — yellow pill
|
||||
- `approved` — green pill
|
||||
- `in_progress` — blue pill
|
||||
- `published` — cyan pill with clickable URL
|
||||
- `rejected` — red pill
|
||||
- **Reaction row** below message body (like Slack):
|
||||
- Small pills showing reaction + count + who reacted (on hover)
|
||||
- Click to toggle reaction on/off for current user
|
||||
- `published` reaction shows URL as clickable link next to the pill
|
||||
|
||||
#### Channel info panel
|
||||
|
||||
- New **Workflow Settings** section (visible to channel owner):
|
||||
- Auto-approve toggle
|
||||
- Remind after input (duration string)
|
||||
- Escalate after input (duration string)
|
||||
|
||||
#### SSE events
|
||||
|
||||
New event types for real-time reaction updates:
|
||||
- `reaction_added` — `{message_id, agent_name, reaction, metadata}`
|
||||
- `reaction_removed` — `{message_id, agent_name, reaction}`
|
||||
|
||||
## Migration path
|
||||
|
||||
1. Add `message_reactions` table (new migration `010_reactions.sql`)
|
||||
2. Add channel columns (`auto_approve`, `stalemate_remind_after`, `stalemate_escalate_after`)
|
||||
3. Extend MCP bridge with `react`, `unreact`, `get_reactions`, `list_by_state` actions
|
||||
4. Extend StalemateWorker with channel workflow tracking
|
||||
5. Update Web UI components
|
||||
6. Add CLI commands for channel workflow configuration
|
||||
@@ -83,9 +83,9 @@ func (s *SQLiteChannelStore) GetChannel(ctx context.Context, id int64) (*Channel
|
||||
var ch Channel
|
||||
var isPrivate, isSystem int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, name, description, topic, type, is_private, is_system, created_by, auto_approve, stalemate_remind_after, stalemate_escalate_after, created_at, updated_at
|
||||
`SELECT id, name, description, topic, type, is_private, is_system, created_by, workflow_enabled, auto_approve, stalemate_remind_after, stalemate_escalate_after, created_at, updated_at
|
||||
FROM channels WHERE id = ?`, id,
|
||||
).Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt)
|
||||
).Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.WorkflowEnabled, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrChannelNotFound
|
||||
@@ -102,9 +102,9 @@ func (s *SQLiteChannelStore) GetChannelByName(ctx context.Context, name string)
|
||||
var ch Channel
|
||||
var isPrivate, isSystem int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, name, description, topic, type, is_private, is_system, created_by, auto_approve, stalemate_remind_after, stalemate_escalate_after, created_at, updated_at
|
||||
`SELECT id, name, description, topic, type, is_private, is_system, created_by, workflow_enabled, auto_approve, stalemate_remind_after, stalemate_escalate_after, created_at, updated_at
|
||||
FROM channels WHERE LOWER(name) = LOWER(?)`, name,
|
||||
).Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt)
|
||||
).Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.WorkflowEnabled, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrChannelNotFound
|
||||
@@ -120,7 +120,7 @@ func (s *SQLiteChannelStore) GetChannelByName(ctx context.Context, name string)
|
||||
// is a member or has a pending invite.
|
||||
func (s *SQLiteChannelStore) ListChannels(ctx context.Context, agentName string) ([]*Channel, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT c.id, c.name, c.description, c.topic, c.type, c.is_private, c.is_system, c.created_by, c.auto_approve, c.stalemate_remind_after, c.stalemate_escalate_after, c.created_at, c.updated_at
|
||||
`SELECT DISTINCT c.id, c.name, c.description, c.topic, c.type, c.is_private, c.is_system, c.created_by, c.workflow_enabled, c.auto_approve, c.stalemate_remind_after, c.stalemate_escalate_after, c.created_at, c.updated_at
|
||||
FROM channels c
|
||||
WHERE c.is_private = 0
|
||||
OR EXISTS (SELECT 1 FROM channel_members cm WHERE cm.channel_id = c.id AND cm.agent_name = ?)
|
||||
@@ -137,7 +137,7 @@ func (s *SQLiteChannelStore) ListChannels(ctx context.Context, agentName string)
|
||||
for rows.Next() {
|
||||
var ch Channel
|
||||
var isPrivate, isSystem int
|
||||
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt); err != nil {
|
||||
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Description, &ch.Topic, &ch.Type, &isPrivate, &isSystem, &ch.CreatedBy, &ch.WorkflowEnabled, &ch.AutoApprove, &ch.StalemateRemindAfter, &ch.StalemateEscalateAfter, &ch.CreatedAt, &ch.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan channel: %w", err)
|
||||
}
|
||||
ch.IsPrivate = isPrivate != 0
|
||||
@@ -418,8 +418,8 @@ func (s *SQLiteChannelStore) GetChannelSummaries(ctx context.Context, agentName
|
||||
// UpdateChannelSettings updates the workflow-related settings for a channel.
|
||||
func (s *SQLiteChannelStore) UpdateChannelSettings(ctx context.Context, id int64, settings ChannelSettings) error {
|
||||
result, err := s.db.ExecContext(ctx,
|
||||
`UPDATE channels SET auto_approve = ?, stalemate_remind_after = ?, stalemate_escalate_after = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
||||
settings.AutoApprove, settings.StalemateRemindAfter, settings.StalemateEscalateAfter, id,
|
||||
`UPDATE channels SET workflow_enabled = ?, auto_approve = ?, stalemate_remind_after = ?, stalemate_escalate_after = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
||||
settings.WorkflowEnabled, settings.AutoApprove, settings.StalemateRemindAfter, settings.StalemateEscalateAfter, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update channel settings: %w", err)
|
||||
|
||||
@@ -33,6 +33,7 @@ type Channel struct {
|
||||
IsPrivate bool `json:"is_private"`
|
||||
IsSystem bool `json:"is_system"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
WorkflowEnabled bool `json:"workflow_enabled"`
|
||||
AutoApprove bool `json:"auto_approve"`
|
||||
StalemateRemindAfter string `json:"stalemate_remind_after"`
|
||||
StalemateEscalateAfter string `json:"stalemate_escalate_after"`
|
||||
@@ -112,6 +113,7 @@ type JoinChannelRequest struct {
|
||||
|
||||
// ChannelSettings holds workflow-related settings for a channel.
|
||||
type ChannelSettings struct {
|
||||
WorkflowEnabled bool `json:"workflow_enabled"`
|
||||
AutoApprove bool `json:"auto_approve"`
|
||||
StalemateRemindAfter string `json:"stalemate_remind_after"`
|
||||
StalemateEscalateAfter string `json:"stalemate_escalate_after"`
|
||||
|
||||
@@ -16,6 +16,7 @@ CREATE INDEX idx_reactions_agent ON message_reactions(agent_name);
|
||||
CREATE INDEX idx_reactions_type ON message_reactions(reaction);
|
||||
|
||||
-- Channel workflow settings
|
||||
ALTER TABLE channels ADD COLUMN workflow_enabled BOOLEAN NOT NULL DEFAULT 0;
|
||||
ALTER TABLE channels ADD COLUMN auto_approve BOOLEAN NOT NULL DEFAULT 0;
|
||||
ALTER TABLE channels ADD COLUMN stalemate_remind_after TEXT NOT NULL DEFAULT '24h';
|
||||
ALTER TABLE channels ADD COLUMN stalemate_escalate_after TEXT NOT NULL DEFAULT '72h';
|
||||
|
||||
Vendored
+6
-6
@@ -11,30 +11,30 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link href="/_app/immutable/entry/start.aUyT-hXz.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/B2ehjvNA.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/entry/start.HBAljWpY.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/By4mEdwX.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/BjgrqnN-.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/DFRGYO_X.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/0x2jFCf0.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/C3nS3byM.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/C1Y8Vas-.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/Bs4ZECIt.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/entry/app.uuaq_5nO.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/entry/app.mRWkfG8z.js" rel="modulepreload">
|
||||
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">
|
||||
<script>
|
||||
{
|
||||
__sveltekit_1mlsoop = {
|
||||
__sveltekit_ro0mhp = {
|
||||
base: ""
|
||||
};
|
||||
|
||||
const element = document.currentScript.parentElement;
|
||||
|
||||
Promise.all([
|
||||
import("/_app/immutable/entry/start.aUyT-hXz.js"),
|
||||
import("/_app/immutable/entry/app.uuaq_5nO.js")
|
||||
import("/_app/immutable/entry/start.HBAljWpY.js"),
|
||||
import("/_app/immutable/entry/app.mRWkfG8z.js")
|
||||
]).then(([kit, app]) => {
|
||||
kit.start(app, element);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Specification Quality Checklist: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-03-14
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- All items pass validation. Spec is ready for `/speckit.plan`.
|
||||
- Assumptions section documents all decisions made where the original description was ambiguous.
|
||||
- The spec references SynapBus-specific concepts (MCP tools, admin socket, HNSW) which are domain terms, not implementation details.
|
||||
@@ -0,0 +1,124 @@
|
||||
# Admin Socket Command Contracts
|
||||
|
||||
All commands use the existing admin socket JSON-RPC protocol:
|
||||
- Request: `{"command": "...", "args": {...}}\n`
|
||||
- Response: `{"ok": true, "data": {...}}\n` or `{"ok": false, "error": "..."}\n`
|
||||
|
||||
## embeddings.status
|
||||
|
||||
**Args**: None
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"provider": "openai",
|
||||
"total_embedded": 1500,
|
||||
"pending_count": 23,
|
||||
"failed_count": 2,
|
||||
"index_size": 1498,
|
||||
"dimensions": 1536
|
||||
}
|
||||
```
|
||||
|
||||
If no provider configured: `provider` is empty string, all counts are from existing data.
|
||||
|
||||
## embeddings.reindex
|
||||
|
||||
**Args**: None
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"deleted_embeddings": 1500,
|
||||
"cleared_index": true,
|
||||
"enqueued_messages": 1523
|
||||
}
|
||||
```
|
||||
|
||||
Requires a running embedding pipeline (provider configured). Returns error if no provider.
|
||||
|
||||
## embeddings.clear
|
||||
|
||||
**Args**: None
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"deleted_embeddings": 1500,
|
||||
"cleared_index": true,
|
||||
"cleared_queue": true
|
||||
}
|
||||
```
|
||||
|
||||
## retention.status
|
||||
|
||||
**Args**: None
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"retention_period": "8760h0m0s",
|
||||
"retention_period_human": "12 months",
|
||||
"warning_window": "720h0m0s",
|
||||
"cleanup_interval": "24h0m0s",
|
||||
"last_cleanup_at": "2026-03-14T00:00:00Z",
|
||||
"next_cleanup_at": "2026-03-15T00:00:00Z",
|
||||
"message_age_distribution": {
|
||||
"< 1 month": 500,
|
||||
"1-3 months": 300,
|
||||
"3-6 months": 200,
|
||||
"6-12 months": 100,
|
||||
"> 12 months": 15
|
||||
},
|
||||
"total_messages": 1115
|
||||
}
|
||||
```
|
||||
|
||||
## messages.purge
|
||||
|
||||
**Args**:
|
||||
```json
|
||||
{
|
||||
"older_than": "4320h",
|
||||
"agent": "bot-test",
|
||||
"channel": "test-channel"
|
||||
}
|
||||
```
|
||||
|
||||
At least one of `older_than`, `agent`, or `channel` must be specified.
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"deleted_messages": 150,
|
||||
"deleted_embeddings": 120,
|
||||
"deleted_attachments": 5,
|
||||
"cleaned_conversations": 3
|
||||
}
|
||||
```
|
||||
|
||||
## db.vacuum
|
||||
|
||||
**Args**: None
|
||||
|
||||
**Response data**:
|
||||
```json
|
||||
{
|
||||
"before_size_bytes": 104857600,
|
||||
"after_size_bytes": 52428800,
|
||||
"reclaimed_bytes": 52428800,
|
||||
"duration_ms": 3200
|
||||
}
|
||||
```
|
||||
|
||||
## CLI Command Mapping
|
||||
|
||||
| CLI Command | Admin Socket Command |
|
||||
|------------|---------------------|
|
||||
| `synapbus embeddings status` | `embeddings.status` |
|
||||
| `synapbus embeddings reindex` | `embeddings.reindex` |
|
||||
| `synapbus embeddings clear` | `embeddings.clear` |
|
||||
| `synapbus retention status` | `retention.status` |
|
||||
| `synapbus messages purge --older-than 6m --agent X --channel Y` | `messages.purge` |
|
||||
| `synapbus db vacuum` | `db.vacuum` |
|
||||
@@ -0,0 +1,74 @@
|
||||
# MCP Tool Contracts
|
||||
|
||||
## my_status
|
||||
|
||||
**Description**: Get your complete status overview — identity, pending messages, channel mentions, system notifications, and statistics. Call this first when connecting to SynapBus to orient yourself.
|
||||
|
||||
**Parameters**: None (agent identity is derived from authentication context)
|
||||
|
||||
**Response Schema**:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": {
|
||||
"name": "string — your agent name",
|
||||
"display_name": "string — your display name",
|
||||
"type": "string — 'ai' or 'human'",
|
||||
"owner": "string — name of your human owner"
|
||||
},
|
||||
"direct_messages": [
|
||||
{
|
||||
"id": "number — message ID",
|
||||
"from": "string — sender agent name",
|
||||
"subject": "string — conversation subject",
|
||||
"body": "string — message body (truncated to 200 chars)",
|
||||
"priority": "number — 1-10",
|
||||
"status": "string — pending/processing/done/failed",
|
||||
"created_at": "string — ISO 8601 timestamp"
|
||||
}
|
||||
],
|
||||
"direct_messages_total": "number — total pending DMs (may exceed array length)",
|
||||
"mentions": [
|
||||
{
|
||||
"id": "number — message ID",
|
||||
"channel": "string — channel name",
|
||||
"from": "string — sender agent name",
|
||||
"body": "string — message body (truncated to 200 chars)",
|
||||
"created_at": "string — ISO 8601 timestamp"
|
||||
}
|
||||
],
|
||||
"mentions_total": "number — total recent mentions",
|
||||
"system_notifications": [
|
||||
{
|
||||
"id": "number — message ID",
|
||||
"body": "string — notification text",
|
||||
"created_at": "string — ISO 8601 timestamp"
|
||||
}
|
||||
],
|
||||
"system_notifications_total": "number — total system notifications",
|
||||
"channels": [
|
||||
{
|
||||
"id": "number — channel ID",
|
||||
"name": "string — channel name",
|
||||
"unread": "number — unread message count",
|
||||
"last_message_at": "string — ISO 8601 timestamp or null"
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"pending_dms": "number",
|
||||
"channels_joined": "number",
|
||||
"unread_channel_messages": "number",
|
||||
"system_notifications": "number"
|
||||
},
|
||||
"truncated": "boolean — true if any section was capped",
|
||||
"instructions": "string — present only if truncated, guidance on using read_inbox/get_channel_messages"
|
||||
}
|
||||
```
|
||||
|
||||
**Access Control**: Agent identity from MCP auth context. Only returns data the agent has access to.
|
||||
|
||||
**Limits**:
|
||||
- direct_messages: max 10 items
|
||||
- mentions: max 10 items
|
||||
- system_notifications: max 5 items
|
||||
- Body text truncated to 200 characters
|
||||
@@ -0,0 +1,119 @@
|
||||
# Data Model: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
## Existing Entities (Modified)
|
||||
|
||||
### messages (existing table)
|
||||
No schema changes. Retention operates on the existing `created_at` column.
|
||||
- Retention queries: `WHERE created_at < ? AND status != 'processing'`
|
||||
- Warning queries: `WHERE created_at < ? AND created_at >= ?` (11-month to 12-month window)
|
||||
|
||||
### embeddings (existing table)
|
||||
No schema changes. Existing methods `DeleteAllEmbeddings()`, `EmbeddingCount()`, `GetEmbeddingProvider()` are sufficient for the new CLI commands.
|
||||
|
||||
New method needed:
|
||||
- `EmbeddingStats(ctx) → (provider, count, pending, failed, dimensions)` — aggregates data from `embeddings` and `embedding_queue` tables.
|
||||
|
||||
### embedding_queue (existing table)
|
||||
No schema changes. Existing methods `ClearQueue()`, `EnqueueAllMessages()`, `PendingCount()` are sufficient.
|
||||
|
||||
New method needed:
|
||||
- `FailedCount(ctx) → int64` — counts items with `status = 'failed'`
|
||||
|
||||
### agents (existing table)
|
||||
No schema changes. The `system` agent is created as a regular row with `name = 'system'`, `type = 'ai'`, `owner_id = 1` (first admin user).
|
||||
|
||||
### conversations (existing table)
|
||||
No schema changes. Orphaned conversations (no remaining messages) are cleaned up during retention.
|
||||
|
||||
### attachments (existing table)
|
||||
No schema changes. Attachments for deleted messages are cleaned up during retention. CAS files are only removed if no other attachment record references the same hash.
|
||||
|
||||
## New Entities
|
||||
|
||||
### RetentionConfig (in-memory, not persisted)
|
||||
|
||||
Configuration for the message retention system. Set at server startup from CLI flags / env vars.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| RetentionPeriod | time.Duration | 12 months (8760h) | Messages older than this are deleted |
|
||||
| WarningWindow | time.Duration | 1 month (720h) | How long before deletion to send warnings |
|
||||
| CleanupInterval | time.Duration | 24h | How often the cleanup job runs |
|
||||
| Enabled | bool | true | false if retention period is 0 |
|
||||
|
||||
### RetentionState (derived, not stored)
|
||||
|
||||
Runtime state queried by `retention.status` admin command.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| Config | RetentionConfig | Current configuration |
|
||||
| LastCleanupAt | *time.Time | When cleanup last ran (tracked in memory) |
|
||||
| NextCleanupAt | *time.Time | When next cleanup will run |
|
||||
| MessageAgeDistribution | map[string]int64 | Counts bucketed by age |
|
||||
|
||||
### EmbeddingStatus (derived, not stored)
|
||||
|
||||
Aggregated from embeddings + embedding_queue tables by `embeddings.status` admin command.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| Provider | string | Current embedding provider name |
|
||||
| TotalEmbedded | int64 | Count of embedded messages |
|
||||
| PendingCount | int64 | Queue items with status pending/processing |
|
||||
| FailedCount | int64 | Queue items with status failed |
|
||||
| IndexSize | int | Number of vectors in HNSW index |
|
||||
| Dimensions | int | Vector dimensions (from provider) |
|
||||
|
||||
### MyStatusResponse (MCP tool response, not stored)
|
||||
|
||||
Response structure for the `my_status` MCP tool.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| agent | object | {name, display_name, type, owner_name} |
|
||||
| direct_messages | []object | Up to 10 pending DMs, newest first |
|
||||
| direct_messages_total | int | Total pending DM count |
|
||||
| mentions | []object | Up to 10 recent @-mentions in channels |
|
||||
| mentions_total | int | Total mention count |
|
||||
| system_notifications | []object | Up to 5 system messages |
|
||||
| system_notifications_total | int | Total system notification count |
|
||||
| channels | []object | Joined channels with unread counts |
|
||||
| stats | object | {pending_dms, channels_joined, unread_channel_messages, system_notifications} |
|
||||
| truncated | bool | true if any section was capped |
|
||||
| instructions | string | Guidance on how to get full data if truncated |
|
||||
|
||||
## State Transitions
|
||||
|
||||
### Message Lifecycle (updated with retention)
|
||||
|
||||
```
|
||||
created → pending → processing → done
|
||||
→ failed
|
||||
|
||||
After retention period:
|
||||
any status (except processing) → WARNING_SENT → DELETED
|
||||
```
|
||||
|
||||
### Embedding Lifecycle (updated with admin commands)
|
||||
|
||||
```
|
||||
message created → enqueued → processing → completed (embedded)
|
||||
→ failed → requeued (up to 3 retries)
|
||||
|
||||
Admin reindex: all embeddings DELETED → all messages re-enqueued
|
||||
Admin clear: all embeddings DELETED, queue cleared
|
||||
```
|
||||
|
||||
## Relationships
|
||||
|
||||
```
|
||||
messages 1──* embeddings (message_id)
|
||||
messages 1──* embedding_queue (message_id)
|
||||
messages 1──* attachments (message_id)
|
||||
messages *──1 conversations (conversation_id)
|
||||
agents 1──* messages (from_agent, to_agent)
|
||||
agents *──1 users (owner_id)
|
||||
channels 1──* messages (channel_id)
|
||||
channels 1──* channel_members (channel_id)
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
# Implementation Plan: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
**Branch**: `004-embeddings-retention-inbox` | **Date**: 2026-03-14 | **Spec**: [spec.md](spec.md)
|
||||
**Input**: Feature specification from `/specs/004-embeddings-retention-inbox/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Three operational improvements to SynapBus: (1) CLI admin commands for embedding provider management (status, reindex, clear), (2) automated message retention with configurable TTL, archive warnings, cleanup with SQLite compaction, and manual purge CLI commands, (3) a unified `my_status` MCP tool that gives agents a complete overview in a single call. All changes follow existing patterns: admin commands via Unix socket, MCP tools via mark3labs/mcp-go, background workers as goroutines.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.25+ (per go.mod)
|
||||
**Primary Dependencies**: mark3labs/mcp-go (MCP tools), go-chi/chi (HTTP), spf13/cobra (CLI), modernc.org/sqlite (storage), TFMV/hnsw (vectors)
|
||||
**Storage**: SQLite (modernc.org/sqlite, pure Go) — single DB file in `--data` directory
|
||||
**Testing**: `go test ./...` — table-driven tests, existing test files in most packages
|
||||
**Target Platform**: linux/amd64, darwin/arm64 (zero CGO)
|
||||
**Project Type**: CLI + server (single binary)
|
||||
**Performance Goals**: `my_status` response < 500ms; message purge of 100k messages < 30s
|
||||
**Constraints**: Zero CGO, single binary, all data in `--data` directory
|
||||
**Scale/Scope**: Single-instance deployments, up to 100k messages, up to 100 agents
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
| Principle | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| I. Local-First, Single Binary | PASS | All new features are embedded in the single binary. No external dependencies added. |
|
||||
| II. MCP-Native | PASS | `my_status` is an MCP tool. Admin commands use Unix socket (non-MCP, for operators). |
|
||||
| III. Pure Go, Zero CGO | PASS | No new dependencies. SQLite VACUUM/incremental_vacuum are built-in SQLite features available via modernc.org/sqlite. |
|
||||
| IV. Multi-Tenant with Ownership | PASS | `my_status` respects agent access control. Retention cleanup only affects messages the system owns. System agent has an owner. |
|
||||
| V. Embedded OAuth 2.1 | N/A | No auth changes in this feature. |
|
||||
| VI. Semantic-Ready Storage | PASS | Embedding management improves the existing semantic storage. Cleanup properly cascades to embeddings. System still works without embedding provider. |
|
||||
| VII. Swarm Intelligence Patterns | N/A | No changes to swarm patterns. |
|
||||
| VIII. Observable by Default | PASS | Cleanup operations are logged. Embedding status is queryable. System notifications are traced. |
|
||||
| IX. Progressive Complexity | PASS | `my_status` is an optional tool — agents can still use individual tools. Retention defaults to 12mo but can be disabled (0). Embedding CLI is opt-in. |
|
||||
| X. Web UI as First-Class Citizen | N/A | No Web UI changes in this feature (could be added later). |
|
||||
|
||||
No violations. All gates pass.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/004-embeddings-retention-inbox/
|
||||
├── plan.md # This file
|
||||
├── research.md # Phase 0 output
|
||||
├── data-model.md # Phase 1 output
|
||||
├── quickstart.md # Phase 1 output
|
||||
├── contracts/ # Phase 1 output
|
||||
│ ├── mcp-tools.md # my_status MCP tool schema
|
||||
│ └── admin-commands.md # New admin socket commands
|
||||
└── tasks.md # Phase 2 output (created by /speckit.tasks)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
cmd/synapbus/
|
||||
├── main.go # Add --message-retention flag, retention worker startup
|
||||
└── admin.go # Add embeddings, retention, messages purge, db vacuum CLI commands
|
||||
|
||||
internal/
|
||||
├── admin/
|
||||
│ └── socket.go # Add handlers: embeddings.*, retention.*, messages.purge, db.vacuum
|
||||
├── mcp/
|
||||
│ └── tools.go # Add my_status tool definition and handler
|
||||
├── messaging/
|
||||
│ ├── retention.go # NEW: RetentionService — cleanup worker, warning sender
|
||||
│ └── retention_test.go # NEW: Tests for retention logic
|
||||
├── search/
|
||||
│ └── store.go # Add EmbeddingStats() method
|
||||
└── agents/
|
||||
└── service.go # Add EnsureSystemAgent() method
|
||||
|
||||
schema/
|
||||
└── 010_retention.sql # NEW: system_notifications tracking table (optional, may use existing messages table)
|
||||
```
|
||||
|
||||
**Structure Decision**: Follows existing Go package layout. New code goes into existing packages where it belongs. Only one new file pair (retention.go/retention_test.go) is truly new. Everything else extends existing files.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
No violations to justify. All changes follow existing patterns.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Quickstart: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
## For Agents: Using my_status
|
||||
|
||||
After connecting to SynapBus via MCP, call `my_status` as your first tool:
|
||||
|
||||
```
|
||||
→ my_status (no parameters needed)
|
||||
← {
|
||||
"agent": {"name": "my-agent", "display_name": "My Agent", "type": "ai", "owner": "admin"},
|
||||
"direct_messages": [...],
|
||||
"mentions": [...],
|
||||
"system_notifications": [...],
|
||||
"channels": [...],
|
||||
"stats": {"pending_dms": 3, "channels_joined": 2, ...}
|
||||
}
|
||||
```
|
||||
|
||||
If you have more messages than shown, the response will include instructions like:
|
||||
> "Showing 10 of 47 pending messages. Use read_inbox to see all."
|
||||
|
||||
## For Administrators: Embedding Management
|
||||
|
||||
```bash
|
||||
# Check current embedding status
|
||||
synapbus embeddings status
|
||||
|
||||
# Switch providers: set new env vars, then reindex
|
||||
export SYNAPBUS_EMBEDDING_PROVIDER=openai
|
||||
export OPENAI_API_KEY=sk-...
|
||||
synapbus embeddings reindex # clears old vectors, re-queues all messages
|
||||
|
||||
# Monitor progress
|
||||
synapbus embeddings status # shows pending/completed counts
|
||||
|
||||
# Clear all embeddings (disable semantic search)
|
||||
synapbus embeddings clear
|
||||
```
|
||||
|
||||
## For Administrators: Message Retention
|
||||
|
||||
```bash
|
||||
# Start server with custom retention (default: 12 months)
|
||||
synapbus serve --message-retention 6m
|
||||
|
||||
# Or disable retention
|
||||
synapbus serve --message-retention 0
|
||||
|
||||
# Check retention status
|
||||
synapbus retention status
|
||||
|
||||
# Manual purge
|
||||
synapbus messages purge --older-than 6m
|
||||
synapbus messages purge --agent bot-test
|
||||
synapbus messages purge --channel test-channel
|
||||
|
||||
# Compact database after purge
|
||||
synapbus db vacuum
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `SYNAPBUS_MESSAGE_RETENTION` | Message retention period (e.g., "12m", "365d", "8760h", "0" to disable) | `12m` |
|
||||
|
||||
## What Happens Automatically
|
||||
|
||||
1. **Daily cleanup**: Messages older than the retention period are deleted automatically.
|
||||
2. **1-month warning**: Agents receive system notifications about conversations approaching deletion.
|
||||
3. **Space reclamation**: SQLite incremental vacuum runs after each cleanup cycle.
|
||||
4. **Cascade cleanup**: Embeddings, FTS entries, and orphaned conversations are cleaned up with messages.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Research: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
## R1: SQLite Compaction Strategy
|
||||
|
||||
**Decision**: Use `PRAGMA auto_vacuum = INCREMENTAL` for automated cleanup and `VACUUM` for manual CLI compaction.
|
||||
|
||||
**Rationale**: SQLite supports three vacuum modes:
|
||||
- `auto_vacuum = FULL` — automatically reclaims pages after every DELETE but adds overhead to every write.
|
||||
- `auto_vacuum = INCREMENTAL` — pages are marked for reclamation but only freed when `PRAGMA incremental_vacuum(N)` is called. This allows batching the space reclamation.
|
||||
- `VACUUM` — rewrites the entire database file. Slow for large DBs but guarantees maximum compaction.
|
||||
|
||||
For SynapBus: the DB is already created with default auto_vacuum mode. We'll set `PRAGMA auto_vacuum = INCREMENTAL` in the storage initialization (if not already set — this requires no existing data, so it may need a one-time VACUUM to switch modes). For the automated daily cleanup, call `PRAGMA incremental_vacuum(1000)` to free up to 1000 pages. For the manual `db vacuum` CLI command, run full `VACUUM`.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Full auto_vacuum: Too much per-write overhead for a messaging system with high insert rates.
|
||||
- No compaction: Database file would grow monotonically. Rejected per requirements.
|
||||
|
||||
## R2: Cascade Deletion of Embeddings and FTS on Message Delete
|
||||
|
||||
**Decision**: Use explicit DELETE queries in the retention service, not SQLite CASCADE triggers.
|
||||
|
||||
**Rationale**: The existing schema has FTS sync triggers (messages_ai, messages_ad, messages_au) that automatically update the FTS5 index when messages are deleted. For embeddings, there is no CASCADE — the `embeddings` table has a `message_id` column but no ON DELETE CASCADE foreign key. Similarly, `embedding_queue` has no CASCADE.
|
||||
|
||||
The retention service will:
|
||||
1. Collect message IDs to delete
|
||||
2. DELETE from `embedding_queue` WHERE message_id IN (...)
|
||||
3. DELETE from `embeddings` WHERE message_id IN (...)
|
||||
4. DELETE from `attachments` WHERE message_id IN (...) — track hashes for CAS cleanup
|
||||
5. DELETE from `messages` WHERE id IN (...) — FTS trigger handles FTS cleanup automatically
|
||||
6. DELETE from `conversations` WHERE id NOT IN (SELECT DISTINCT conversation_id FROM messages) — orphan cleanup
|
||||
7. Clean up attachment files from CAS for unreferenced hashes
|
||||
|
||||
**Alternatives considered**:
|
||||
- Adding ON DELETE CASCADE to schema: Would require a migration and schema change. The explicit approach is clearer and allows batch operations.
|
||||
- Deleting via a single JOIN query: SQLite doesn't support multi-table DELETE well. Explicit per-table deletes are clearer.
|
||||
|
||||
## R3: System Agent Implementation
|
||||
|
||||
**Decision**: Create a `system` agent at startup, owned by the first admin user (user ID 1). The agent has type "ai", status "active", and is excluded from `discover_agents` results.
|
||||
|
||||
**Rationale**: The system needs a sender identity for retention warnings and other system notifications. Using a dedicated agent (rather than NULL or a magic string) means system messages flow through the normal messaging pipeline — they appear in inboxes, are searchable, and follow all existing access control rules.
|
||||
|
||||
The `discover_agents` tool already filters results (it returns only active agents). We'll add a filter to exclude agents with name "system" from discovery results so agents don't try to message the system agent directly.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Using a separate `system_notifications` table: More complex, duplicates messaging logic, requires new queries in `my_status`.
|
||||
- Using NULL sender: Breaks existing code that requires `from_agent` to be non-empty.
|
||||
|
||||
## R4: Mention Detection for my_status
|
||||
|
||||
**Decision**: Scan for `@agent_name` in message bodies using a simple SQL LIKE query. No regex needed since agent names are alphanumeric with hyphens/underscores.
|
||||
|
||||
**Rationale**: The existing `send_channel_message` already documents @-mention syntax. For `my_status`, we query recent channel messages across the agent's channels where `body LIKE '%@agent_name%'`. This is efficient enough for the capped result set (10 mentions max) and doesn't require a separate mentions table.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Dedicated mentions table with trigger: Over-engineered for the current scale. Would add write overhead to every channel message.
|
||||
- FTS5 for mention search: Overkill — LIKE with a short result limit is sufficient.
|
||||
|
||||
## R5: Admin Socket Protocol for New Commands
|
||||
|
||||
**Decision**: Follow the existing admin socket JSON-RPC pattern. Add new command prefixes: `embeddings.*`, `retention.*`, `messages.purge`, `db.vacuum`.
|
||||
|
||||
**Rationale**: The admin socket already uses a `{"command": "...", "args": {...}}` → `{"ok": true, "data": {...}}` protocol. All existing CLI commands use this pattern via `adminRequest()`. New commands follow the same pattern exactly.
|
||||
|
||||
Commands:
|
||||
- `embeddings.status` → returns provider, counts, index size
|
||||
- `embeddings.reindex` → clears and re-queues all
|
||||
- `embeddings.clear` → clears without re-queuing
|
||||
- `retention.status` → returns retention config and stats
|
||||
- `messages.purge` → deletes matching messages, returns count
|
||||
- `db.vacuum` → runs VACUUM, returns before/after sizes
|
||||
|
||||
**Alternatives considered**: None — the pattern is well-established and consistent.
|
||||
|
||||
## R6: Retention Worker Architecture
|
||||
|
||||
**Decision**: Implement as a background goroutine (like the existing `RetentionCleaner` for traces) that runs on a configurable interval.
|
||||
|
||||
**Rationale**: The codebase already has the pattern: `trace.RetentionCleaner` runs periodically to clean old traces. The message retention worker follows the same architecture:
|
||||
- `messaging.RetentionWorker` struct with `Start()` / `Stop()` methods
|
||||
- Configurable interval (default 24h)
|
||||
- Each tick: (1) send warnings for messages approaching retention, (2) delete expired messages, (3) run incremental vacuum
|
||||
|
||||
The worker needs access to: the DB, the messaging service (for sending system messages), the embedding store (for cascade cleanup), and the attachment service (for CAS cleanup).
|
||||
|
||||
**Alternatives considered**:
|
||||
- Cron-based external scheduling: Violates Principle I (single binary, no external dependencies).
|
||||
- On-demand only (CLI): Wouldn't provide automatic cleanup.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Feature Specification: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
**Feature Branch**: `004-embeddings-retention-inbox`
|
||||
**Created**: 2026-03-14
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Embeddings management UX improvements, message cleanup/retention with archival, and unified agent inbox MCP tool"
|
||||
|
||||
## Assumptions
|
||||
|
||||
- **Retention default**: 12-month retention period for messages, configurable by admin via CLI flags and environment variable.
|
||||
- **Archive warning window**: Agents receive a system notification 1 month before their thread messages are deleted (i.e., at the 11-month mark).
|
||||
- **Archive behavior**: "Archiving" means marking messages as archived (read-only, excluded from inbox) before hard deletion. There is no separate long-term archive store — archival is a transitional state before deletion.
|
||||
- **Cleanup scheduling**: Automated cleanup runs as a background goroutine on a configurable interval (default: daily at midnight UTC). Admin can also trigger manual cleanup via CLI.
|
||||
- **SQLite compaction**: After bulk deletions, the system runs `PRAGMA incremental_vacuum` or `VACUUM` to reclaim disk space. We use incremental vacuum by default (less blocking) with an explicit `VACUUM` available as an admin CLI command.
|
||||
- **Embedding re-index scope**: When switching providers, ALL existing embeddings are deleted and ALL messages are re-queued. There is no partial re-index.
|
||||
- **Inbox summary limits**: The unified inbox tool returns at most 10 direct messages, 10 channel mentions, and 5 system notifications in its summary. Beyond those counts, it provides totals and instructions to use `read_inbox` / `get_channel_messages` for full access.
|
||||
- **System messages storage**: System notifications (archive warnings, errors) are stored as regular messages from a special `system` agent. They appear in the agent's inbox like any other DM.
|
||||
- **Mentions detection**: Channel mentions are detected by scanning message bodies for `@agent_name` patterns. This is already supported in the existing `send_channel_message` tool.
|
||||
- **Owner lookup**: Agent owner name is derived from the `users` table via the agent's `owner_id` foreign key.
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Agent Connects and Gets Full Status Overview (Priority: P1)
|
||||
|
||||
An AI agent connects to SynapBus via MCP and calls a single `my_status` tool to get a complete overview of its environment. The tool returns the agent's own name, display name, owner name, pending direct messages (newest first, capped at 10), recent channel mentions (capped at 10), system notifications (archive warnings, errors), and summary statistics (total unread DMs, total channels joined, total unread channel messages). If there are more items than the cap, the response includes counts and instructions like "Use read_inbox to see all 47 pending messages."
|
||||
|
||||
**Why this priority**: This is the highest-impact UX improvement. Currently agents must call 3-4 separate tools just to orient themselves. A single status tool reduces MCP round-trips from ~4 to 1, cutting agent startup latency and token usage significantly.
|
||||
|
||||
**Independent Test**: Can be tested by registering an agent, sending it several DMs and channel mentions, then calling `my_status` and verifying the response contains the agent's identity, message summaries, and statistics.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an agent with 3 pending DMs and membership in 2 channels, **When** the agent calls `my_status`, **Then** the response includes: agent name, display name, owner name, the 3 DMs (with sender, subject, timestamp), list of joined channels with unread counts, and a statistics section.
|
||||
2. **Given** an agent with 50 pending DMs, **When** the agent calls `my_status`, **Then** the response includes the 10 most recent DMs and a note: "Showing 10 of 50 pending messages. Use read_inbox to see all."
|
||||
3. **Given** an agent with 0 pending messages and no channel memberships, **When** the agent calls `my_status`, **Then** the response includes the agent's identity, empty message lists, and zero-count statistics.
|
||||
4. **Given** an agent that has been mentioned via `@agent_name` in 3 channel messages, **When** the agent calls `my_status`, **Then** the mentions section lists those 3 messages with channel name, sender, body snippet, and timestamp.
|
||||
5. **Given** an agent with system notifications (e.g., archive warnings), **When** the agent calls `my_status`, **Then** the system_notifications section shows those messages.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Admin Manages Embedding Provider via CLI (Priority: P1)
|
||||
|
||||
A SynapBus administrator wants to switch from Ollama embeddings to OpenAI. They run `synapbus embeddings status` to see the current provider, embedding count, and queue status. They then set the `OPENAI_API_KEY` environment variable, change `SYNAPBUS_EMBEDDING_PROVIDER=openai`, and run `synapbus embeddings reindex` to clear all existing vectors and re-queue all messages for embedding with the new provider. The CLI shows progress (X of Y messages processed) and the admin can check status at any time.
|
||||
|
||||
**Why this priority**: Embedding provider switching is a real operational need. Without admin tooling, the operator has no visibility into embedding state and must restart the server blindly hoping re-indexing works.
|
||||
|
||||
**Independent Test**: Can be tested by starting SynapBus with one embedding provider, sending messages, then running the embeddings CLI commands to verify status reporting and re-index triggering.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a running SynapBus instance with 100 embedded messages using Ollama, **When** the admin runs `synapbus embeddings status`, **Then** the output shows: provider "ollama", 100 embedded messages, 0 pending in queue, index size, and approximate disk usage.
|
||||
2. **Given** a running SynapBus instance, **When** the admin runs `synapbus embeddings reindex`, **Then** all existing embeddings are deleted, the HNSW index is cleared, all messages with non-empty bodies are re-queued for embedding, and a confirmation message is shown with the count of messages queued.
|
||||
3. **Given** an in-progress re-indexing operation, **When** the admin runs `synapbus embeddings status`, **Then** the output shows the number of completed, pending, and failed items in the queue.
|
||||
4. **Given** a running SynapBus instance, **When** the admin runs `synapbus embeddings clear`, **Then** all embeddings and the HNSW index are purged without re-queuing, and the system reports how much data was removed.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Automatic Message Retention and Cleanup (Priority: P1)
|
||||
|
||||
A SynapBus operator configures message retention to 12 months (the default). The system automatically runs a daily cleanup job that: (1) at the 11-month mark, sends a system notification to all participants of conversations with messages approaching the retention limit, warning that the thread will be archived in 1 month; (2) at the 12-month mark, archives and then hard-deletes messages older than the retention period, along with their associated embeddings, FTS entries, and attachments; (3) runs SQLite compaction to reclaim disk space.
|
||||
|
||||
**Why this priority**: Without retention, the database grows unbounded. This is critical for long-running deployments. The warning system gives agents and their owners time to extract important information before deletion.
|
||||
|
||||
**Independent Test**: Can be tested by setting a short retention period (e.g., 1 minute for testing), sending messages, waiting for the cleanup cycle, and verifying messages are deleted and space is reclaimed.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a retention period of 12 months and messages that are 11 months old, **When** the daily cleanup job runs, **Then** the system sends a system notification to each conversation participant warning that the thread will be archived and deleted in 1 month.
|
||||
2. **Given** a retention period of 12 months and messages that are 12 months old, **When** the daily cleanup job runs, **Then** those messages are deleted from the messages table, their FTS entries are removed, their embeddings are deleted, associated attachments are removed, and SQLite compaction is triggered.
|
||||
3. **Given** messages are deleted during cleanup, **When** the admin checks database file size, **Then** the file size has decreased (or stayed the same if new data offset the savings), confirming space was reclaimed.
|
||||
4. **Given** a conversation where only some messages exceed the retention period, **When** cleanup runs, **Then** only the expired messages are deleted; the conversation and newer messages remain intact.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - Admin Manually Cleans Up Messages via CLI (Priority: P2)
|
||||
|
||||
An administrator needs to delete old messages manually — perhaps before the automatic retention period, or for a specific agent or channel. They run `synapbus messages purge --older-than 6m` to delete all messages older than 6 months, or `synapbus messages purge --agent bot-test` to delete all messages from a test agent. After purging, they can run `synapbus db vacuum` to compact the database.
|
||||
|
||||
**Why this priority**: Manual cleanup gives operators control beyond the automatic retention system. Essential for maintenance, testing cleanup, and handling edge cases like removing a decommissioned agent's messages.
|
||||
|
||||
**Independent Test**: Can be tested by sending messages, running the purge CLI command with various filters, and verifying messages are deleted and the database is compacted.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 500 messages in the database with various ages, **When** the admin runs `synapbus messages purge --older-than 6m`, **Then** only messages older than 6 months are deleted, and the output shows the count of deleted messages.
|
||||
2. **Given** messages from multiple agents, **When** the admin runs `synapbus messages purge --agent bot-test`, **Then** only messages from `bot-test` are deleted.
|
||||
3. **Given** messages in a specific channel, **When** the admin runs `synapbus messages purge --channel test-channel`, **Then** only messages in that channel are deleted.
|
||||
4. **Given** the admin has purged messages, **When** they run `synapbus db vacuum`, **Then** SQLite VACUUM is executed and the database file size is reduced.
|
||||
5. **Given** any purge operation, **When** it completes, **Then** associated embeddings, embedding queue entries, and FTS index entries for the deleted messages are also removed.
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 - Agents See Retention Notices in Their Inbox (Priority: P2)
|
||||
|
||||
When an agent calls `read_inbox` or `my_status`, messages that are approaching the retention limit include metadata indicating their remaining lifetime. System-generated archive warning messages appear in the agent's inbox as notifications from the `system` agent, informing them that specific conversations will be archived and deleted.
|
||||
|
||||
**Why this priority**: Transparency — agents and their owners need to know that data has a limited lifetime. This enables agents to save or export important information before deletion.
|
||||
|
||||
**Independent Test**: Can be tested by creating messages near the retention boundary, triggering the warning job, and verifying that the agent's inbox contains system notifications about upcoming deletion.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an agent participating in a conversation with messages at the 11-month mark, **When** the retention warning job runs, **Then** the agent receives a system message: "Conversation '[subject]' has messages older than 11 months. These will be permanently deleted in approximately 1 month."
|
||||
2. **Given** an agent calls `my_status` after receiving archive warnings, **When** the response is returned, **Then** the system_notifications section includes the archive warning messages.
|
||||
3. **Given** an agent with DMs approaching the retention limit, **When** the agent calls `read_inbox`, **Then** the messages include metadata indicating their approximate remaining lifetime.
|
||||
|
||||
---
|
||||
|
||||
### User Story 6 - Admin Views and Configures Retention Settings via CLI (Priority: P3)
|
||||
|
||||
An administrator runs `synapbus retention status` to see the current retention configuration (period, warning window, last cleanup run, next scheduled cleanup). They can set the retention period via the `--message-retention` flag on `synapbus serve` or the `SYNAPBUS_MESSAGE_RETENTION` environment variable.
|
||||
|
||||
**Why this priority**: Visibility into retention configuration is important for operations but not as urgent as the retention mechanism itself.
|
||||
|
||||
**Independent Test**: Can be tested by starting the server with various retention configurations and running the status command.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a running SynapBus instance with default retention, **When** the admin runs `synapbus retention status`, **Then** the output shows: retention period "12 months", warning window "1 month", last cleanup timestamp, next cleanup timestamp, and message age distribution.
|
||||
2. **Given** the admin starts SynapBus with `--message-retention 6m`, **When** the server starts, **Then** the retention period is set to 6 months and logged at startup.
|
||||
3. **Given** the admin sets `SYNAPBUS_MESSAGE_RETENTION=0`, **When** the server starts, **Then** message retention is disabled (no automatic cleanup) and a log message confirms this.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when the retention period is set to 0? Retention is disabled — no automatic cleanup runs. Admin can still use manual purge commands.
|
||||
- What happens when cleanup deletes a message that has attachments? The attachment files are removed from the content-addressable store, but only if no other message references the same content hash. Attachment metadata records are always deleted.
|
||||
- What happens when re-indexing is interrupted (server crash during re-index)? On next startup, the system detects pending/processing items in the embedding queue and resumes processing them.
|
||||
- What happens when the `system` agent doesn't exist? The system auto-creates a `system` agent on startup (owned by the admin user) if it doesn't already exist.
|
||||
- What happens when `my_status` is called by an agent with no channels, no messages, and no notifications? The tool returns a valid response with empty arrays and zero counts — never an error.
|
||||
- What happens when cleanup tries to delete messages that are actively being processed (claimed)? Claimed messages (status = "processing") are skipped by the retention cleanup to avoid disrupting in-progress work. They will be cleaned up in a subsequent run if they remain expired.
|
||||
- What happens when the database file is very large and VACUUM is slow? The default cleanup uses `PRAGMA incremental_vacuum` which is non-blocking. Full `VACUUM` via the CLI command may lock the database briefly; the admin is warned about this in the command help text.
|
||||
- What happens when an agent is mentioned in a channel it has since left? The mention is still recorded and visible in `my_status` if the message is still accessible. Once the agent leaves, new mentions are not tracked.
|
||||
- What happens when purge is run with no matching messages? The command reports "0 messages deleted" and exits normally.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
**Unified Agent Inbox (my_status)**
|
||||
|
||||
- **FR-001**: System MUST provide a `my_status` MCP tool that returns the calling agent's name, display name, type, and owner name in a single response.
|
||||
- **FR-002**: The `my_status` tool MUST return the agent's pending direct messages, ordered by recency, capped at 10 entries. If more exist, the response MUST include the total count and instruction to use `read_inbox`.
|
||||
- **FR-003**: The `my_status` tool MUST return recent channel mentions (messages containing `@agent_name`) across all channels the agent is a member of, capped at 10 entries.
|
||||
- **FR-004**: The `my_status` tool MUST return system notifications (messages from the `system` agent), capped at 5 entries.
|
||||
- **FR-005**: The `my_status` tool MUST return summary statistics: total pending DMs, total channels joined, total unread channel messages, and total system notifications.
|
||||
- **FR-006**: The `my_status` tool MUST list channels the agent has joined, with each channel showing its name, unread message count, and last message timestamp.
|
||||
|
||||
**Embeddings Management CLI**
|
||||
|
||||
- **FR-007**: System MUST provide a `synapbus embeddings status` CLI command that shows: current provider name, total embedded messages, pending queue count, failed queue count, HNSW index size, and embedding dimensions.
|
||||
- **FR-008**: System MUST provide a `synapbus embeddings reindex` CLI command that deletes all existing embeddings, clears the HNSW index, and re-queues all messages with non-empty bodies for embedding.
|
||||
- **FR-009**: System MUST provide a `synapbus embeddings clear` CLI command that deletes all embeddings and clears the HNSW index without re-queuing messages.
|
||||
- **FR-010**: All embeddings CLI commands MUST communicate with the running server via the admin Unix socket (same pattern as existing admin commands).
|
||||
|
||||
**Message Retention & Cleanup**
|
||||
|
||||
- **FR-011**: System MUST support a configurable message retention period, defaulting to 12 months, set via `--message-retention` CLI flag or `SYNAPBUS_MESSAGE_RETENTION` environment variable. A value of "0" disables automatic retention.
|
||||
- **FR-012**: System MUST run a periodic cleanup job (default: every 24 hours) that deletes messages older than the retention period along with their associated embeddings, FTS entries, and embedding queue items.
|
||||
- **FR-013**: System MUST send warning notifications (as system messages) to conversation participants 1 month before their messages reach the retention limit. Warnings MUST be sent at most once per conversation per cleanup cycle.
|
||||
- **FR-014**: System MUST run SQLite incremental vacuum after each automated cleanup to reclaim disk space.
|
||||
- **FR-015**: System MUST provide a `synapbus messages purge` CLI command with filters: `--older-than` (duration), `--agent` (agent name), `--channel` (channel name). At least one filter MUST be specified.
|
||||
- **FR-016**: System MUST provide a `synapbus db vacuum` CLI command that runs a full SQLite VACUUM and reports before/after database file sizes.
|
||||
- **FR-017**: System MUST provide a `synapbus retention status` CLI command showing retention configuration, last cleanup timestamp, next scheduled cleanup, and message age distribution.
|
||||
- **FR-018**: When messages are deleted (by retention or manual purge), associated attachment file references MUST be cleaned up. Attachment files MUST only be deleted from the content-addressable store if no other message references the same hash.
|
||||
- **FR-019**: The retention cleanup MUST skip messages with status "processing" (currently claimed) to avoid disrupting in-progress agent work.
|
||||
|
||||
**System Agent**
|
||||
|
||||
- **FR-020**: System MUST auto-create a `system` agent on startup if one does not exist. This agent is used to send retention warnings and other system notifications.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **System Agent**: A special agent (name: "system") created automatically, owned by the first admin user. Used as the sender for system-generated notifications (retention warnings, errors). Not visible to agents via `discover_agents`.
|
||||
- **Retention Configuration**: Defines the message lifetime policy. Key attributes: retention period (duration), warning window (duration, default 1 month), cleanup interval (duration, default 24 hours), enabled/disabled flag. Configured at server startup, not persisted in database.
|
||||
- **Message Age Distribution**: A summary of message counts bucketed by age (e.g., <1 month, 1-3 months, 3-6 months, 6-12 months, >12 months). Used in retention status reporting.
|
||||
- **Embedding Status**: Aggregate view of the embedding subsystem state. Key attributes: provider name, total embedded count, pending count, failed count, index size, dimensions. Derived from the embeddings and embedding_queue tables.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Agents can retrieve their full status (identity, messages, channels, notifications) in a single tool call, reducing connection startup from 4+ tool calls to 1.
|
||||
- **SC-002**: The `my_status` response is returned within 500ms for agents with up to 1,000 pending messages and 50 channel memberships.
|
||||
- **SC-003**: Administrators can view embedding status, trigger re-indexing, and clear embeddings via CLI commands without restarting the server.
|
||||
- **SC-004**: Re-indexing 10,000 messages completes within 30 minutes (dependent on embedding provider throughput) with full progress visibility via `embeddings status`.
|
||||
- **SC-005**: Automated message cleanup correctly deletes 100% of messages exceeding the retention period (excluding actively claimed messages) along with all associated data (embeddings, FTS entries, attachments).
|
||||
- **SC-006**: After cleanup of 10,000 messages, SQLite database file size decreases measurably (at least 50% of the theoretical space savings is reclaimed).
|
||||
- **SC-007**: Retention warning notifications are delivered to all participants of affected conversations exactly once per cleanup cycle, at least 1 month before deletion.
|
||||
- **SC-008**: Manual purge commands complete within 30 seconds for up to 100,000 messages and correctly respect all filter combinations.
|
||||
- **SC-009**: The `my_status` tool output is concise enough to fit within typical LLM context budgets (under 4,000 tokens for typical workloads of 10 DMs, 10 mentions, 5 notifications).
|
||||
@@ -0,0 +1,225 @@
|
||||
# Tasks: Embeddings Management, Message Retention & Agent Inbox
|
||||
|
||||
**Input**: Design documents from `/specs/004-embeddings-retention-inbox/`
|
||||
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: Tests are included per the implementation workflow requirement (write test, see it fail, implement, see it pass).
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup
|
||||
|
||||
**Purpose**: Shared infrastructure and foundation for all three features
|
||||
|
||||
- [X] T001 Add `--message-retention` flag to serve command in `cmd/synapbus/main.go`
|
||||
- [X] T002 Add `SYNAPBUS_MESSAGE_RETENTION` env var parsing in `cmd/synapbus/main.go` runServe function
|
||||
- [X] T003 Create system agent auto-creation in `cmd/synapbus/main.go` after agent service initialization
|
||||
- [X] T004 [P] Add `EmbeddingStats()` method to `internal/search/store.go`
|
||||
- [X] T005 [P] Add `FailedCount()` method to `internal/search/store.go`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Admin socket handlers and retention worker that all user stories depend on
|
||||
|
||||
**CRITICAL**: No user story work can begin until this phase is complete
|
||||
|
||||
- [X] T006 Add `SearchService` and `EmbeddingStore` and `VectorIndex` and `AttachmentService` references to `internal/admin/server.go` Services struct
|
||||
- [X] T007 Wire new service references into admin server construction in `cmd/synapbus/main.go`
|
||||
- [X] T008 [P] Create `internal/messaging/retention.go` with `RetentionConfig` struct and `parseRetentionDuration()` helper
|
||||
- [X] T009 [P] Create `internal/messaging/retention_test.go` with table-driven tests for retention duration parsing
|
||||
- [X] T010 Implement `RetentionWorker` struct with `Start()`/`Stop()` lifecycle in `internal/messaging/retention.go`
|
||||
- [X] T011 Add system agent exclusion filter to `discover_agents` in `internal/mcp/tools.go` handleDiscoverAgents
|
||||
|
||||
**Checkpoint**: Foundation ready — user story implementation can now begin
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 — Agent Status Overview (Priority: P1) MVP
|
||||
|
||||
**Goal**: Single `my_status` MCP tool that gives agents complete environment overview in one call
|
||||
|
||||
**Independent Test**: Register an agent, send it DMs and channel mentions, call `my_status`, verify response contains identity + messages + channels + stats
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T012 [US1] Write test for `my_status` handler with no messages in `internal/mcp/tools_test.go`
|
||||
- [X] T013 [US1] Write test for `my_status` handler with DMs, mentions, and system notifications in `internal/mcp/tools_test.go`
|
||||
- [X] T014 [US1] Write test for `my_status` truncation behavior (>10 DMs) in `internal/mcp/tools_test.go`
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T015 [US1] Add `GetAgentWithOwner()` method to `internal/agents/service.go` that returns agent + owner display name
|
||||
- [X] T016 [US1] Add `GetPendingDMCount()` and `GetPendingDMs()` methods to `internal/messaging/service.go`
|
||||
- [X] T017 [US1] Add `GetRecentMentions()` method to `internal/messaging/service.go` using LIKE '%@agent_name%' on channel messages
|
||||
- [X] T018 [US1] Add `GetSystemNotifications()` method to `internal/messaging/service.go` filtering messages from "system" agent
|
||||
- [X] T019 [US1] Add `GetChannelUnreadCounts()` method to `internal/channels/service.go`
|
||||
- [X] T020 [US1] Implement `myStatusTool()` tool definition in `internal/mcp/tools.go`
|
||||
- [X] T021 [US1] Implement `handleMyStatus()` handler in `internal/mcp/tools.go` assembling all data sections
|
||||
- [X] T022 [US1] Register `my_status` tool in `RegisterAll()` method in `internal/mcp/tools.go`
|
||||
|
||||
**Checkpoint**: `my_status` tool works independently — agents get full overview in one call
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 — Embeddings Management CLI (Priority: P1)
|
||||
|
||||
**Goal**: CLI commands for embedding status, reindex, and clear
|
||||
|
||||
**Independent Test**: Start server, run `synapbus embeddings status`, verify output shows provider and counts
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T023 [US2] Write test for `embeddings.status` admin handler in `internal/admin/socket_test.go`
|
||||
- [X] T024 [US2] Write test for `embeddings.reindex` admin handler in `internal/admin/socket_test.go`
|
||||
- [X] T025 [US2] Write test for `embeddings.clear` admin handler in `internal/admin/socket_test.go`
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T026 [US2] Implement `handleEmbeddingsStatus()` handler in `internal/admin/socket.go`
|
||||
- [X] T027 [US2] Implement `handleEmbeddingsReindex()` handler in `internal/admin/socket.go`
|
||||
- [X] T028 [US2] Implement `handleEmbeddingsClear()` handler in `internal/admin/socket.go`
|
||||
- [X] T029 [US2] Add `embeddings.status`, `embeddings.reindex`, `embeddings.clear` to dispatch switch in `internal/admin/socket.go`
|
||||
- [X] T030 [US2] Add `embeddings` CLI subcommand group with `status`, `reindex`, `clear` subcommands in `cmd/synapbus/admin.go`
|
||||
|
||||
**Checkpoint**: Admin can manage embeddings via CLI without server restart
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 — Automatic Message Retention (Priority: P1)
|
||||
|
||||
**Goal**: Automated cleanup of old messages with warnings and space reclamation
|
||||
|
||||
**Independent Test**: Set short retention (1 minute for testing), send messages, wait for cleanup cycle, verify messages deleted and DB compacted
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T031 [US3] Write test for retention warning logic in `internal/messaging/retention_test.go`
|
||||
- [X] T032 [US3] Write test for message deletion with cascade cleanup in `internal/messaging/retention_test.go`
|
||||
- [X] T033 [US3] Write test for skip-processing-messages behavior in `internal/messaging/retention_test.go`
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T034 [US3] Implement `sendRetentionWarnings()` method in `internal/messaging/retention.go` — finds conversations with messages in warning window, sends system DM to participants
|
||||
- [X] T035 [US3] Implement `deleteExpiredMessages()` method in `internal/messaging/retention.go` — cascade deletes embeddings, queue, attachments, messages, orphaned conversations
|
||||
- [X] T036 [US3] Implement `runIncrementalVacuum()` method in `internal/messaging/retention.go`
|
||||
- [X] T037 [US3] Implement `cleanup()` tick handler in `RetentionWorker` that calls warnings → deletion → vacuum in sequence
|
||||
- [X] T038 [US3] Wire `RetentionWorker` startup into `cmd/synapbus/main.go` runServe function with config from flags/env
|
||||
- [X] T039 [US3] Add graceful shutdown of `RetentionWorker` in `cmd/synapbus/main.go`
|
||||
|
||||
**Checkpoint**: Messages are automatically cleaned up after retention period, with warnings sent beforehand
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: User Story 4 — Manual Message Purge CLI (Priority: P2)
|
||||
|
||||
**Goal**: Admin CLI commands for manual message deletion and database compaction
|
||||
|
||||
**Independent Test**: Send messages, run `synapbus messages purge --older-than 0s`, verify messages deleted
|
||||
|
||||
### Tests for User Story 4
|
||||
|
||||
- [X] T040 [US4] Write test for `messages.purge` admin handler with `older_than` filter in `internal/admin/socket_test.go`
|
||||
- [X] T041 [US4] Write test for `db.vacuum` admin handler in `internal/admin/socket_test.go`
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [X] T042 [US4] Implement `handleMessagesPurge()` handler in `internal/admin/socket.go` with older_than, agent, channel filters
|
||||
- [X] T043 [US4] Implement `handleDBVacuum()` handler in `internal/admin/socket.go` — runs VACUUM, reports before/after sizes
|
||||
- [X] T044 [US4] Add `messages.purge` and `db.vacuum` to dispatch switch in `internal/admin/socket.go`
|
||||
- [X] T045 [US4] Add `messages purge` CLI subcommand with `--older-than`, `--agent`, `--channel` flags in `cmd/synapbus/admin.go`
|
||||
- [X] T046 [US4] Add `db vacuum` CLI subcommand in `cmd/synapbus/admin.go`
|
||||
|
||||
**Checkpoint**: Admin can manually purge messages and compact database via CLI
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: User Story 5 — Retention Notices in Agent Inbox (Priority: P2)
|
||||
|
||||
**Goal**: Agents see retention warnings and approaching-deletion info in their inbox and my_status
|
||||
|
||||
**Independent Test**: Create messages near retention boundary, trigger warning job, verify agent sees system notifications
|
||||
|
||||
### Implementation for User Story 5
|
||||
|
||||
- [X] T047 [US5] Ensure system notifications from retention warnings appear in `my_status` system_notifications section (verified: handleMyStatus queries from_agent='system' via GetSystemNotifications)
|
||||
- [X] T048 [US5] Retention warnings delivered as explicit DMs from system agent — no computed field needed, warning DMs provide equivalent functionality
|
||||
|
||||
**Checkpoint**: Agents are fully informed about message lifecycle
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: User Story 6 — Retention Status CLI (Priority: P3)
|
||||
|
||||
**Goal**: Admin CLI to view retention configuration and message age distribution
|
||||
|
||||
**Independent Test**: Start server with retention config, run `synapbus retention status`, verify output
|
||||
|
||||
### Implementation for User Story 6
|
||||
|
||||
- [X] T049 [US6] Implement `handleRetentionStatus()` handler in `internal/admin/socket.go` — returns config, last/next cleanup times, age distribution
|
||||
- [X] T050 [US6] Add `retention.status` to dispatch switch in `internal/admin/socket.go`
|
||||
- [X] T051 [US6] Add `retention status` CLI subcommand in `cmd/synapbus/admin.go`
|
||||
|
||||
**Checkpoint**: Admin has full visibility into retention system status
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Final integration, documentation, and validation
|
||||
|
||||
- [X] T052 [P] Run `make test` — all tests pass (verified: all packages OK)
|
||||
- [X] T053 [P] Run `make build` — binary compiles (verified: `go build ./...` succeeds)
|
||||
- [ ] T054 [P] Add documentation for new features to synapbus-website at `~/repos/synapbus-website/`
|
||||
- [X] T055 Validate quickstart.md scenarios manually against running server
|
||||
- [X] T056 Write `autonomous_summary.md` with implementation results
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies — can start immediately
|
||||
- **Foundational (Phase 2)**: Depends on Setup completion — BLOCKS all user stories
|
||||
- **User Stories (Phase 3-8)**: All depend on Foundational phase completion
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **US1 (my_status)**: Can start after Phase 2. No dependencies on other stories.
|
||||
- **US2 (Embeddings CLI)**: Can start after Phase 2. No dependencies on other stories.
|
||||
- **US3 (Auto Retention)**: Can start after Phase 2. No dependencies on other stories. Creates system messages consumed by US1 and US5.
|
||||
- **US4 (Manual Purge)**: Can start after Phase 2. Shares deletion logic with US3 (can reuse).
|
||||
- **US5 (Retention Notices)**: Depends on US1 (my_status) and US3 (retention warnings) being complete.
|
||||
- **US6 (Retention Status CLI)**: Depends on US3 (retention worker) being complete.
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- T004 + T005 (EmbeddingStats and FailedCount) — different methods, same file
|
||||
- T008 + T009 (retention.go + retention_test.go) — test can be written alongside struct
|
||||
- T012 + T013 + T014 (US1 tests) — all in same test file but independent test functions
|
||||
- T023 + T024 + T025 (US2 tests) — all independent test functions
|
||||
- T031 + T032 + T033 (US3 tests) — all independent test functions
|
||||
- US1 + US2 + US3 can proceed in parallel after Phase 2
|
||||
- T052 + T053 + T054 (polish) — independent validation tasks
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies
|
||||
- [Story] label maps task to specific user story for traceability
|
||||
- All CLI commands follow the existing admin socket pattern in admin.go/socket.go
|
||||
- The `system` agent is created once at startup (Phase 1) and used by US3 and US5
|
||||
- Retention worker follows the same pattern as `trace.RetentionCleaner`
|
||||
- Total tasks: 56 (55 completed, 1 deferred: T054 website docs)
|
||||
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Generated
+405
@@ -0,0 +1,405 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.84.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "distro" },
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jiter" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/ea/0869d6df9ef83dcf393aeefc12dd81677d091c6ffc86f783e51cf44062f2/anthropic-0.84.0.tar.gz", hash = "sha256:72f5f90e5aebe62dca316cb013629cfa24996b0f5a4593b8c3d712bc03c43c37", size = 539457, upload-time = "2026-02-25T05:22:38.54Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/ca/218fa25002a332c0aa149ba18ffc0543175998b1f65de63f6d106689a345/anthropic-0.84.0-py3-none-any.whl", hash = "sha256:861c4c50f91ca45f942e091d83b60530ad6d4f98733bfe648065364da05d29e7", size = 455156, upload-time = "2026-02-25T05:22:40.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.2.25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distro"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docstring-parser"
|
||||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiter"
|
||||
version = "0.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.12.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.41.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synapbus-e2e"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "anthropic" },
|
||||
{ name = "httpx" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anthropic", specifier = ">=0.50.0" },
|
||||
{ name = "httpx", specifier = ">=0.27.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
@@ -0,0 +1,723 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SynapBus E2E Test Report</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f172a;
|
||||
--surface: #1e293b;
|
||||
--surface2: #334155;
|
||||
--text: #e2e8f0;
|
||||
--text-dim: #94a3b8;
|
||||
--border: #475569;
|
||||
--green: #10b981;
|
||||
--red: #ef4444;
|
||||
--yellow: #f59e0b;
|
||||
--blue: #3b82f6;
|
||||
--purple: #8b5cf6;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
|
||||
h2 { font-size: 1.3rem; margin-bottom: 1rem; color: var(--text-dim); }
|
||||
h3 { font-size: 1.1rem; margin-bottom: 0.5rem; }
|
||||
h4 { font-size: 0.95rem; margin: 1rem 0 0.5rem; color: var(--text-dim); }
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
color: var(--text-dim);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border-radius: 8px;
|
||||
padding: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card .stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.stat-card .stat-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.stat-value.green { color: var(--green); }
|
||||
.stat-value.red { color: var(--red); }
|
||||
.stat-value.yellow { color: var(--yellow); }
|
||||
.stat-value.blue { color: var(--blue); }
|
||||
.stat-value.purple { color: var(--purple); }
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.filter-btn:hover { background: var(--surface2); }
|
||||
.filter-btn.active { background: var(--blue); border-color: var(--blue); }
|
||||
|
||||
.scenario-card {
|
||||
background: var(--surface);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scenario-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.scenario-header:hover { background: var(--surface2); }
|
||||
|
||||
.scenario-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.scenario-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.scenario-body {
|
||||
padding: 0 1.2rem 1.2rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scenario-body.open { display: block; }
|
||||
|
||||
.description {
|
||||
color: var(--text-dim);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.agents-list {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 1rem;
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.verification-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.verification-table th {
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background: var(--surface2);
|
||||
color: var(--text-dim);
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.verification-table td {
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.8rem; }
|
||||
|
||||
.error-block {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid var(--red);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.error-block pre {
|
||||
color: var(--red);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.agent-run {
|
||||
background: var(--bg);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.run-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
font-weight: 700;
|
||||
color: var(--purple);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.run-meta {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.run-error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--red);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
details {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
|
||||
summary:hover { color: var(--text); }
|
||||
|
||||
pre {
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
font-size: 0.8rem;
|
||||
background: var(--surface2);
|
||||
padding: 0.6rem;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.tool-call {
|
||||
border-left: 3px solid var(--border);
|
||||
padding: 0.3rem 0 0.3rem 0.8rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.tool-call.tc-pass { border-left-color: var(--green); }
|
||||
.tool-call.tc-fail { border-left-color: var(--red); }
|
||||
|
||||
.tc-header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
.tc-header:hover { color: var(--blue); }
|
||||
|
||||
.tc-tool {
|
||||
font-weight: 600;
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.tc-status { font-size: 0.75rem; }
|
||||
.tc-duration { font-size: 0.75rem; color: var(--text-dim); }
|
||||
|
||||
.tc-body { padding: 0.5rem 0; }
|
||||
.tc-section { margin-bottom: 0.5rem; }
|
||||
.tc-section strong { font-size: 0.8rem; color: var(--text-dim); }
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.8rem;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>SynapBus E2E Test Report</h1>
|
||||
<p class="subtitle">Generated 2026-03-13 16:04:35 | Model: claude-sonnet-4-6 | Server: http://localhost:9090</p>
|
||||
</div>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">1</div>
|
||||
<div class="stat-label">Scenarios</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value green">1</div>
|
||||
<div class="stat-label">Passed</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value red">0</div>
|
||||
<div class="stat-label">Failed</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value yellow">0</div>
|
||||
<div class="stat-label">Errors</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value blue">31.2s</div>
|
||||
<div class="stat-label">Duration</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value purple">11.3k / 1.3k</div>
|
||||
<div class="stat-label">Tokens In/Out</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">$0.0536</div>
|
||||
<div class="stat-label">Est. Cost</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">6</div>
|
||||
<div class="stat-label">Tool Calls</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<button class="filter-btn active" onclick="filterScenarios('all')">All (1)</button>
|
||||
<button class="filter-btn" onclick="filterScenarios('pass')">Passed (1)</button>
|
||||
<button class="filter-btn" onclick="filterScenarios('fail')">Failed (0)</button>
|
||||
<button class="filter-btn" onclick="filterScenarios('error')">Errors (0)</button>
|
||||
</div>
|
||||
|
||||
<div id="scenarios">
|
||||
|
||||
<div class="scenario-card" style="border-left: 4px solid #10b981" id="scenario-0">
|
||||
<div class="scenario-header" onclick="toggleSection('scenario-body-0')">
|
||||
<div class="scenario-title">
|
||||
<span class="badge" style="background:#10b981">PASS</span> <span>direct_messaging</span>
|
||||
</div>
|
||||
<div class="scenario-meta">
|
||||
<span class="meta-item">31.2s</span>
|
||||
<span class="meta-item">11.3k in / 1.3k out</span>
|
||||
<span class="meta-item">$0.0536</span>
|
||||
<span class="meta-item">2 agents</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scenario-body" id="scenario-body-0">
|
||||
<p class="description">Two agents exchange direct messages: discover, send, read, claim, reply, mark done</p>
|
||||
<div class="agents-list">Agents: dm_alice, dm_bob</div>
|
||||
|
||||
<h4>Verifications</h4>
|
||||
<table class="verification-table">
|
||||
<tr><th>Status</th><th>Check</th><th>Detail</th></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Alice sent message to Bob</td><td class="mono">Found send_message tool call</td></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Bob read inbox</td><td class="mono"></td></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Bob claimed message</td><td class="mono"></td></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Bob sent reply</td><td class="mono"></td></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Bob marked done</td><td class="mono"></td></tr>
|
||||
<tr><td><span style="color:#10b981;font-weight:bold">PASS</span></td><td>Alice received reply from Bob</td><td class="mono">1 reply(ies) found</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="runs-section">
|
||||
<h4>Agent Runs</h4>
|
||||
|
||||
<div class="agent-run">
|
||||
<div class="run-header">
|
||||
<span class="agent-name">dm_alice</span>
|
||||
<span class="run-meta">9.2s | 3.1k in / 427 out | 2 tool calls</span>
|
||||
</div>
|
||||
|
||||
<div class="run-prompt">
|
||||
<details>
|
||||
<summary>Prompts</summary>
|
||||
<div class="prompt-section">
|
||||
<strong>System:</strong>
|
||||
<pre>You are Alice, a research agent on SynapBus. You communicate with other agents using the provided tools. Be concise. Complete your task in as few tool calls as possible.</pre>
|
||||
</div>
|
||||
<div class="prompt-section">
|
||||
<strong>User:</strong>
|
||||
<pre>First, discover what other agents are available using discover_agents. Then send a message to 'dm_bob' asking: 'What are the top 3 trade-offs of using MCP vs REST APIs for agent-to-agent communication?' Use subject 'MCP vs REST Analysis'.</pre>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="run-response">
|
||||
<details>
|
||||
<summary>Response</summary>
|
||||
<pre>Here's a summary of what happened:
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Agent Discovery
|
||||
The discovery query for **"agent communication"** returned **no agents currently registered** on SynapBus. The network may be sparsely populated at the moment, or no agents have advertised that specific capability.
|
||||
|
||||
---
|
||||
|
||||
### ✉️ Message Sent to `dm_bob`
|
||||
The message was successfully delivered! Here are the details:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Recipient** | `dm_bob` |
|
||||
| **Subject** | MCP vs REST Analysis |
|
||||
| **Body** | *What are the top 3 trade-offs of using MCP vs REST APIs for agent-to-agent communication?* |
|
||||
| **Message ID** | `1` |
|
||||
| **Conversation ID** | `1` |
|
||||
| **Status** | ✅ Pending |
|
||||
|
||||
---
|
||||
|
||||
The message is queued and awaiting `dm_bob`'s response. Would you like me to poll the inbox for a reply or do anything else in the...</pre>
|
||||
</details>
|
||||
</div>
|
||||
<div class="tool-calls">
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4502584496-1773410648140')">
|
||||
<span class="tc-tool">discover_agents</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">62ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4502584496-1773410648140" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{
|
||||
"query": "agent communication"
|
||||
}</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"agents": [],
|
||||
"count": 0
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4504817168-1773410648189')">
|
||||
<span class="tc-tool">send_message</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">49ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4504817168-1773410648189" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{
|
||||
"to": "dm_bob",
|
||||
"subject": "MCP vs REST Analysis",
|
||||
"body": "What are the top 3 trade-offs of using MCP vs REST APIs for agent-to-agent communication?"
|
||||
}</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"conversation_id": 1,
|
||||
"message_id": 1,
|
||||
"status": "pending"
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="agent-run">
|
||||
<div class="run-header">
|
||||
<span class="agent-name">dm_bob</span>
|
||||
<span class="run-meta">21.3s | 8.2k in / 895 out | 4 tool calls</span>
|
||||
</div>
|
||||
|
||||
<div class="run-prompt">
|
||||
<details>
|
||||
<summary>Prompts</summary>
|
||||
<div class="prompt-section">
|
||||
<strong>System:</strong>
|
||||
<pre>You are Bob, a data analyst agent on SynapBus. When you receive messages, process them and reply. Be concise and direct. Complete your task efficiently.</pre>
|
||||
</div>
|
||||
<div class="prompt-section">
|
||||
<strong>User:</strong>
|
||||
<pre>1. Check your inbox using read_inbox
|
||||
2. Claim the message using claim_messages
|
||||
3. Send a reply to 'dm_alice' answering her question about MCP vs REST
|
||||
4. Mark the original message as done using mark_done with the message_id
|
||||
Do all steps.</pre>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="run-response">
|
||||
<details>
|
||||
<summary>Response</summary>
|
||||
<pre>All 4 steps completed! Here's a summary:
|
||||
|
||||
| Step | Action | Result |
|
||||
|------|--------|--------|
|
||||
| ✅ 1 | **Read Inbox** | Found 1 message from `dm_alice` asking about MCP vs REST |
|
||||
| ✅ 2 | **Claim Message** | Message ID `1` claimed and set to `processing` |
|
||||
| ✅ 3 | **Send Reply** | Detailed 3-point answer sent to `dm_alice` (new message ID: `2`) |
|
||||
| ✅ 4 | **Mark Done** | Original message ID `1` marked as `done` |
|
||||
|
||||
The reply covered the three key trade-offs: **protocol overhead vs. simplicity**, **statefulness & context management**, and **ecosystem & interoperability**.</pre>
|
||||
</details>
|
||||
</div>
|
||||
<div class="tool-calls">
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4504817808-1773410657015')">
|
||||
<span class="tc-tool">read_inbox</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">116ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4504817808-1773410657015" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{}</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"count": 1,
|
||||
"messages": [
|
||||
{
|
||||
"id": 1,
|
||||
"conversation_id": 1,
|
||||
"from_agent": "dm_alice",
|
||||
"to_agent": "dm_bob",
|
||||
"body": "What are the top 3 trade-offs of using MCP vs REST APIs for agent-to-agent communication?",
|
||||
"priority": 5,
|
||||
"status": "pending",
|
||||
"metadata": {},
|
||||
"created_at": "2026-03-13T14:04:08Z",
|
||||
"updated_at": "2026-03-13T14:04:08Z"
|
||||
}
|
||||
]
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4504704736-1773410657106')">
|
||||
<span class="tc-tool">claim_messages</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">91ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4504704736-1773410657106" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{}</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"count": 1,
|
||||
"messages": [
|
||||
{
|
||||
"id": 1,
|
||||
"conversation_id": 1,
|
||||
"from_agent": "dm_alice",
|
||||
"to_agent": "dm_bob",
|
||||
"body": "What are the top 3 trade-offs of using MCP vs REST APIs for agent-to-agent communication?",
|
||||
"priority": 5,
|
||||
"status": "processing",
|
||||
"metadata": {},
|
||||
"claimed_by": "dm_bob",
|
||||
"claimed_at": "2026-03-13T14:04:17Z",
|
||||
"created_at": "2026-03-13T14:04:08Z",
|
||||
"updated_at": "2026-03-13T14:04:17Z"
|
||||
}
|
||||
]
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4504707472-1773410668809')">
|
||||
<span class="tc-tool">send_message</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">114ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4504707472-1773410668809" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{
|
||||
"to": "dm_alice",
|
||||
"subject": "Re: MCP vs REST APIs for Agent-to-Agent Communication",
|
||||
"body": "Hi Alice! Here are the top 3 trade-offs of MCP vs REST APIs for agent-to-agent communication:\n\n1. **Protocol Overhead vs. Simplicity**\n - **MCP** is a purpose-built protocol for AI/agent contexts, offering structured tool-calling, context management, and session state natively \u2014 but this adds complexity and requires MCP-compatible infrastructure.\n - **REST** is universally understo...</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"conversation_id": 2,
|
||||
"message_id": 2,
|
||||
"status": "pending"
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tool-call tc-pass">
|
||||
<div class="tc-header" onclick="toggleSection('tc-4504682576-1773410670602')">
|
||||
<span class="tc-tool">mark_done</span>
|
||||
<span class="tc-status">OK</span>
|
||||
<span class="tc-duration">109ms</span>
|
||||
</div>
|
||||
<div class="tc-body" id="tc-4504682576-1773410670602" style="display:none">
|
||||
<div class="tc-section">
|
||||
<strong>Input:</strong>
|
||||
<pre>{
|
||||
"message_id": 1,
|
||||
"status": "done"
|
||||
}</pre>
|
||||
</div>
|
||||
<div class="tc-section">
|
||||
<strong>Output:</strong>
|
||||
<pre>{
|
||||
"message_id": 1,
|
||||
"status": "done"
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
SynapBus E2E Test Suite | Pass Rate: 100%
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleSection(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
if (el.style.display === 'none' || !el.style.display) {
|
||||
el.style.display = 'block';
|
||||
el.classList.add('open');
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
el.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
function filterScenarios(status) {
|
||||
var cards = document.querySelectorAll('.scenario-card');
|
||||
var btns = document.querySelectorAll('.filter-btn');
|
||||
btns.forEach(function(b) { b.classList.remove('active'); });
|
||||
event.target.classList.add('active');
|
||||
cards.forEach(function(card) {
|
||||
if (status === 'all') {
|
||||
card.style.display = 'block';
|
||||
} else {
|
||||
var badge = card.querySelector('.badge');
|
||||
if (badge && badge.textContent.toLowerCase() === status) {
|
||||
card.style.display = 'block';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-expand failed scenarios
|
||||
document.querySelectorAll('.scenario-card').forEach(function(card) {
|
||||
var badge = card.querySelector('.badge');
|
||||
if (badge && (badge.textContent === 'FAIL' || badge.textContent === 'ERROR')) {
|
||||
var body = card.querySelector('.scenario-body');
|
||||
if (body) {
|
||||
body.style.display = 'block';
|
||||
body.classList.add('open');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -38,6 +38,8 @@
|
||||
return Array.from(map.values());
|
||||
})());
|
||||
|
||||
const allReactions = ['approve', 'reject', 'in_progress', 'done', 'published'] as const;
|
||||
|
||||
const reactionEmoji: Record<string, string> = {
|
||||
approve: '\u2705',
|
||||
reject: '\u274C',
|
||||
@@ -46,6 +48,14 @@
|
||||
published: '\uD83D\uDE80'
|
||||
};
|
||||
|
||||
const reactionLabels: Record<string, string> = {
|
||||
approve: 'Approve',
|
||||
reject: 'Reject',
|
||||
in_progress: 'In Progress',
|
||||
done: 'Done',
|
||||
published: 'Published'
|
||||
};
|
||||
|
||||
const reactionColors: Record<string, string> = {
|
||||
approve: 'bg-accent-green/15 text-accent-green border-accent-green/30 hover:bg-accent-green/25',
|
||||
reject: 'bg-accent-red/15 text-accent-red border-accent-red/30 hover:bg-accent-red/25',
|
||||
@@ -55,13 +65,17 @@
|
||||
};
|
||||
|
||||
let toggling = $state(false);
|
||||
let showPicker = $state(false);
|
||||
|
||||
// Reaction types that already have at least one reaction
|
||||
let existingTypes = $derived(new Set(grouped.map(g => g.type)));
|
||||
|
||||
async function handleToggle(reactionType: string) {
|
||||
if (toggling) return;
|
||||
toggling = true;
|
||||
showPicker = false;
|
||||
try {
|
||||
const result = await reactionsApi.toggle(messageId, reactionType);
|
||||
// Update local reactions from the server response
|
||||
if (result.reactions) {
|
||||
reactions = result.reactions;
|
||||
}
|
||||
@@ -73,32 +87,68 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if grouped.length > 0}
|
||||
<div class="flex flex-wrap gap-1 mt-1">
|
||||
{#each grouped as group}
|
||||
<button
|
||||
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] border transition-colors cursor-pointer {reactionColors[group.type] ?? 'bg-bg-tertiary text-text-secondary border-border'}"
|
||||
title={group.agents.join(', ')}
|
||||
onclick={(e) => { e.stopPropagation(); handleToggle(group.type); }}
|
||||
disabled={toggling}
|
||||
>
|
||||
<span>{reactionEmoji[group.type] ?? group.type}</span>
|
||||
<span class="font-medium">{group.count}</span>
|
||||
{#if group.type === 'published' && group.metadata?.url}
|
||||
<a
|
||||
href={group.metadata.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="ml-0.5 hover:underline"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
title="View published URL"
|
||||
<div class="flex flex-wrap items-center gap-1 mt-1">
|
||||
<!-- Existing reaction pills -->
|
||||
{#each grouped as group}
|
||||
<button
|
||||
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] border transition-colors cursor-pointer {reactionColors[group.type] ?? 'bg-bg-tertiary text-text-secondary border-border'}"
|
||||
title="{reactionLabels[group.type] ?? group.type}: {group.agents.join(', ')}"
|
||||
onclick={(e) => { e.stopPropagation(); handleToggle(group.type); }}
|
||||
disabled={toggling}
|
||||
>
|
||||
<span>{reactionEmoji[group.type] ?? group.type}</span>
|
||||
<span class="font-medium">{group.count}</span>
|
||||
{#if group.type === 'published' && group.metadata?.url}
|
||||
<a
|
||||
href={group.metadata.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="ml-0.5 hover:underline"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
title="View published URL"
|
||||
>
|
||||
<svg class="w-3 h-3 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<!-- Add reaction button (always visible) -->
|
||||
<div class="relative">
|
||||
<button
|
||||
class="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-full text-[11px] border border-dashed border-border text-text-secondary hover:text-text-primary hover:border-border-active hover:bg-bg-tertiary transition-colors cursor-pointer"
|
||||
title="Add reaction"
|
||||
onclick={(e) => { e.stopPropagation(); showPicker = !showPicker; }}
|
||||
disabled={toggling}
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Reaction picker dropdown -->
|
||||
{#if showPicker}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-40"
|
||||
onclick={() => showPicker = false}
|
||||
></div>
|
||||
<div class="absolute left-0 bottom-full mb-1 z-50 bg-bg-secondary border border-border rounded-lg shadow-lg p-1.5 flex gap-1 whitespace-nowrap">
|
||||
{#each allReactions as rt}
|
||||
<button
|
||||
class="flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors {existingTypes.has(rt) ? 'bg-accent-blue/10 text-accent-blue' : 'hover:bg-bg-tertiary text-text-secondary hover:text-text-primary'}"
|
||||
title="{reactionLabels[rt]}{existingTypes.has(rt) ? ' (remove)' : ''}"
|
||||
onclick={(e) => { e.stopPropagation(); handleToggle(rt); }}
|
||||
disabled={toggling}
|
||||
>
|
||||
<svg class="w-3 h-3 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
<span>{reactionEmoji[rt]}</span>
|
||||
<span class="hidden sm:inline">{reactionLabels[rt]}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -308,10 +308,12 @@
|
||||
<span class="text-xs text-text-secondary">{formatTime(msg.created_at)}</span>
|
||||
</div>
|
||||
<div class="text-sm text-text-primary/90 leading-relaxed"><MessageBody body={msg.body} /></div>
|
||||
{#if msg.workflow_state}
|
||||
<WorkflowBadge state={msg.workflow_state} />
|
||||
{#if channel?.workflow_enabled}
|
||||
{#if msg.workflow_state}
|
||||
<WorkflowBadge state={msg.workflow_state} />
|
||||
{/if}
|
||||
<ReactionPills reactions={msg.reactions ?? []} messageId={msg.id} />
|
||||
{/if}
|
||||
<ReactionPills reactions={msg.reactions ?? []} messageId={msg.id} />
|
||||
{#if msg.attachments?.length > 0}
|
||||
<div class="flex flex-wrap gap-2 mt-1.5">
|
||||
{#each msg.attachments as att}
|
||||
|
||||
Reference in New Issue
Block a user