@pellux/goodvibes-agent 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
@@ -23,7 +23,6 @@
23
23
  "README.md",
24
24
  "CHANGELOG.md",
25
25
  "docs/*.md",
26
- "!docs/competitive-parity-plan.md",
27
26
  "release/release-notes.md",
28
27
  "release/performance-snapshot.json",
29
28
  "release/release-readiness.json",
@@ -90,7 +89,7 @@
90
89
  },
91
90
  "dependencies": {},
92
91
  "devDependencies": {
93
- "@pellux/goodvibes-sdk": "0.33.38",
92
+ "@pellux/goodvibes-sdk": "0.34.0",
94
93
  "sql.js": "^1.14.1",
95
94
  "sqlite-vec": "^0.1.9",
96
95
  "zustand": "^5.0.12",
@@ -152,7 +152,7 @@
152
152
  "quality": {
153
153
  "capabilityCoverage": "Covers safe project/global VIBE.md personality discovery, direct vibe status/show/init/import-persona model routes, VIBE init/import preview confirmationRoutes, safe project context discovery for .hermes.md, HERMES.md, AGENTS.md, CLAUDE.md, HERMES_HOME/SOUL.md, .cursorrules, and .cursor/rules/*.mdc files, visible Local Context and Personas workspace health counts for VIBE.md/project context files, Local Context prompt receipt outcome timeline with exact drill-in and outcome filters, status/init/show/import-persona flows, setup and learning-curator health cards for blocked/truncated VIBE.md files, opt-in VIBE.md profile starter export/import/application, durable local memory, memory posture, vector/embedding health, supervised Knowledge semantic refinement task inspection, confirmed bounded semantic refinement runs, scratch notes, personas, profiles, search/review/stale/delete flows, duplicate-consolidation preview/merge/stale/delete/rollback/recreate receipts with exact-id post-delete recreate guidance, prompt injection gates for VIBE.md, project context files, and reviewed/confident setup-ready behavior, read-only prompt-context inspection plus sanitized prompt-context receipts for applied order/recent receipt ids/exact receipt-turn-outcome filters/selected records/suppressed records/token budget/prompt hash/terminal outcome, score-driven learning prompt plans for prompt-active/suppressed/proposal/consolidation queues, and promotion without knowledge contamination.",
154
154
  "userAccess": "The user can manage VIBE.md, prompt context inspection with a compact Local Context receipt outcome timeline plus exact latest receipt and outcome filter routes, project context inspection, and records through Agent workspace Local Context, Memory, Notes, Personas, Profiles, /vibe, /memory, /personas, /agent-profile starter flows with --include-vibe, and the Memory posture row.",
155
- "modelAccess": "Runtime prompt context applies secret-scanned VIBE.md files, secret-scanned project context files, and only reviewed memory at the durable confidence threshold plus reviewed setup-ready skills, routines, bundles, and personas while listing suppressed records as review/setup work; vibe action:\"status|show|init|import_persona\" exposes the direct model route for read-only VIBE.md inspection, confirmed VIBE.md creation, and confirmed VIBE.md-to-persona import without default Knowledge writes, and unconfirmed init/import previews expose exact model and CLI confirmationRoutes; runtime prompt builds write durable sanitized prompt-context receipts with ids, turn/source/model/provider, selected and suppressed record refs, segment counts, prompt hash, size, timestamp, and terminal outcome/status/stopReason/bounded error-or-cancel detail without storing raw prompt or response text; context action:\"prompt|receipts|receipt\" exposes applied prompt composition order, recent receipt ids, exact receiptId/turnId/outcomeStatus filters, turn outcomes, selected VIBE.md/project context/memory/routine/skill/persona records, suppressed records, bounded prompt previews when includeParameters is true, and approximate token budget without mutation; Agent workspace Local Context exposes VIBE.md/project context health counts, compact prompt receipt outcome timeline counts/latest rows/detail/exact latest receipt route/outcome filter routes, and action route hints for Prompt context, Inspect VIBE.md, Inspect project context, and Inspect one context file; context action:\"files|file\" exposes loaded, truncated, and blocked .hermes.md/HERMES.md/AGENTS.md/CLAUDE.md/SOUL.md/Cursor context files with target-aware subdirectory lookup and bounded bodies; lower-level agent_harness mode:\"project_context\", mode:\"project_context_file\", and mode:\"prompt_context\" remain available for compatibility/detail coverage; /vibe exposes user-facing status/init/show/import-persona routes and Agent workspace Personas exposes VIBE.md status/init/import commands; /agent-profile and profile CLI starter flows expose vibeIncluded template summaries and opt-in --include-vibe export/from-discovered/profile creation; agent_local_registry exposes record lookup/search/get/create/update/review/use/delete for memory, notes, personas, skills, skill bundles, and routines; memory action:\"status|provider|list|search|get\" exposes local memory counts, direct memory record lookup/search, prompt-active recall, vector stats, embedding-provider doctor warnings, provider inspection, route-planner preference for external provider/backend/sync/import-export setup posture, and external-memory setup contract maps for Honcho, OpenViking, Mem0, Hindsight, Holographic, RetainDB, ByteRover, Supermemory, and daemon-published similar backends with provider-specific next-route packets, certified schema/version/publication/publisher/provenance checks, setup/status/read/write/sync/forget checklist items, required receipt fields, certified durable receipt evidence, and sanitized certified live read-model records when the SDK/daemon publishes concrete setup/status/read/write/sync/forget records for Agent to consume; memory action:\"refinement|run_refinement\" exposes the SDK-backed Agent Knowledge semantic self-improvement task ledger and confirmed bounded KnowledgeService.runRefinement route with space/source/refinement-target scoping, task/source follow-up routes, and no hidden prompt-memory writes; lower-level agent_harness mode:\"memory_posture\", mode:\"memory_provider\", mode:\"memory_refinement\", and mode:\"run_memory_refinement\" remain available for compatibility/detail coverage; agent_harness mode:\"setup_posture\" exposes VIBE.md personality health counts and handoff routes; memory action:\"curator|candidate\" exposes a score-driven prompt plan with prompt-active records, suppressed review/setup/low-confidence/personality/consolidation counts, proposal queues, consolidation queues, ordering rules, and exact review routes plus ranked local review, stale, setup, low-confidence, VIBE.md blocked/truncated health, duplicate-consolidation candidates with visible diffs plus update/stale/delete/rollback/recreate routes, an ordered duplicate-consolidation batch review plan, first-class agent_learning_consolidation preview/merge/stale/delete/rollback/recreate phase routes with durable receipts, delete-after-stale enforcement, deleteRecovery payloads, and exact-id recreate refusal when current records would collide, reviewed-note memory/behavior proposal, completed-work memory/behavior proposal, completed-research memory/behavior proposal, saved-session memory/behavior proposal, and promotion candidates with existing safe routes; lower-level agent_harness mode:\"learning_curator\" and mode:\"learning_candidate\" remain available for compatibility/detail coverage; route planning prefers personal_ops action:\"briefing\" for daily/calendar briefings, action:\"queue\" for saved review queues, action:\"intake\" for fresh provider-read planning, and action:\"lane\" for connector setup posture before live provider reads or effects; personal_ops action:\"briefing|status|queue|intake|lane|read\" exposes the preferred direct route for a read-only daily Personal Ops briefing plan, read-only saved inbox/calendar review queue, request-to-route Personal Ops planning, lane inspection, and one confirmed read-only inbox/calendar operation, while lower-level modes mode:\"personal_ops_briefing\", mode:\"personal_ops_queue\", mode:\"personal_ops_intake\", mode:\"personal_ops\", mode:\"personal_ops_lane\", and mode:\"run_personal_ops_read\" remain available for harness inspection; Personal Ops surfaces Agent-owned note, routine, schedule-receipt, delivery, task, and reminder live records plus honest email/calendar connector readiness, classified MCP read/write tool hints, schema-derived inbox/calendar operation records with required fields/sample inputs/schema routes/confirmation flags/fresh-read routes, inbox triage/draft and calendar agenda/conflict workflow cards with prerequisites/inspect routes/send-edit boundaries, ordered connector-read/local-compose/setup-repair/confirmed-effect execution plans, confirmed read-only MCP inbox/calendar execution with required-field checks, bounded redacted output, normalized review cards, structured next-route packets for lane/queue inspection, confirmed refresh, saved artifact reopen, local draft/reminder work, and confirmed-effect boundaries, optional saved redacted review-card artifacts, saved review artifacts resurfaced as redacted inbox thread/calendar event queue records, certified provider-effect receipts for inbox/calendar/task/reminder outcomes, fresh daemon/SDK provider-backed inbox/calendar/task/reminder queue records with durable ids, labels, redacted snippets/notes, agenda windows, conflict signals, due times, cadence, delivery targets, source paths, freshness status, schema/version/publication/publisher/provenance/receipt evidence, read-only inspect routes, and confirmed follow-up boundaries only for published provider routes, and a unified read-only queue view with artifact inspect routes, freshness status, confirmed refresh routes when a matching read connector is ready, local draft/reminder follow-up routes, certified provider-effect boundaries, task workflow cards for visible work-plan review and connected-host task review, reminder workflow cards for confirmed reminders and connected schedule review, and matching MCP connector setup routes; agent_harness exposes confirmed workspace editor routes when the user starts from visible workspace forms.",
155
+ "modelAccess": "Runtime prompt context applies secret-scanned VIBE.md files, secret-scanned project context files, and only reviewed memory at the durable confidence threshold plus reviewed setup-ready skills, routines, bundles, and personas while listing suppressed records as review/setup work; vibe action:\"status|show|init|import_persona\" exposes the direct model route for read-only VIBE.md inspection, confirmed VIBE.md creation, and confirmed VIBE.md-to-persona import without default Knowledge writes, and unconfirmed init/import previews expose exact model and CLI confirmationRoutes; runtime prompt builds write durable sanitized prompt-context receipts with ids, turn/source/model/provider, selected and suppressed record refs, segment counts, prompt hash, size, timestamp, and terminal outcome/status/stopReason/bounded error-or-cancel detail without storing raw prompt or response text; context action:\"prompt|receipts|receipt\" exposes applied prompt composition order, recent receipt ids, exact receiptId/turnId/outcomeStatus filters, turn outcomes, selected VIBE.md/project context/memory/routine/skill/persona records, suppressed records, bounded prompt previews when includeParameters is true, and approximate token budget without mutation; Agent workspace Local Context exposes VIBE.md/project context health counts, compact prompt receipt outcome timeline counts/latest rows/detail/exact latest receipt route/outcome filter routes, and action route hints for Prompt context, Inspect VIBE.md, Inspect project context, and Inspect one context file; context action:\"files|file\" exposes loaded, truncated, and blocked .hermes.md/HERMES.md/AGENTS.md/CLAUDE.md/SOUL.md/Cursor context files with target-aware subdirectory lookup and bounded bodies; lower-level agent_harness mode:\"project_context\", mode:\"project_context_file\", and mode:\"prompt_context\" remain available for compatibility/detail coverage; /vibe exposes user-facing status/init/show/import-persona routes and Agent workspace Personas exposes VIBE.md status/init/import commands; /agent-profile and profile CLI starter flows expose vibeIncluded template summaries and opt-in --include-vibe export/from-discovered/profile creation; agent_local_registry exposes record lookup/search/get/create/update/review/use/delete for memory, notes, personas, skills, skill bundles, and routines; memory action:\"status|provider|list|search|get\" exposes local memory counts, direct memory record lookup/search, prompt-active recall, vector stats, embedding-provider doctor warnings, provider inspection, route-planner preference for external provider/backend/sync/import-export setup posture, and external-memory setup contract maps for Honcho, OpenViking, Mem0, Hindsight, Holographic, RetainDB, ByteRover, Supermemory, and daemon-published similar backends with provider-specific next-route packets, certified schema/version/publication/publisher/provenance checks, setup/status/read/write/sync/forget checklist items, required receipt fields, certified durable receipt evidence, and sanitized certified live read-model records when the SDK/daemon publishes concrete setup/status/read/write/sync/forget records for Agent to consume; memory action:\"refinement|run_refinement\" exposes the SDK-backed Agent Knowledge semantic self-improvement task ledger and confirmed bounded KnowledgeService.runRefinement route with space/source/refinement-target scoping, task/source follow-up routes, and no hidden prompt-memory writes; lower-level agent_harness mode:\"memory_posture\", mode:\"memory_provider\", mode:\"memory_refinement\", and mode:\"run_memory_refinement\" remain available for compatibility/detail coverage; agent_harness mode:\"setup_posture\" exposes VIBE.md personality health counts and handoff routes; memory action:\"curator|candidate\" exposes a score-driven prompt plan with prompt-active records, suppressed review/setup/low-confidence/personality/consolidation counts, proposal queues, consolidation queues, ordering rules, and exact review routes plus ranked local review, stale, setup, low-confidence, VIBE.md blocked/truncated health, duplicate-consolidation candidates with visible diffs plus update/stale/delete/rollback/recreate routes, an ordered duplicate-consolidation batch review plan, first-class agent_learning_consolidation preview/merge/stale/delete/rollback/recreate phase routes with durable receipts, delete-after-stale enforcement, deleteRecovery payloads, and exact-id recreate refusal when current records would collide, reviewed-note memory/behavior proposal, completed-work memory/behavior proposal, completed-research memory/behavior proposal, saved-session memory/behavior proposal, and promotion candidates with existing safe routes; lower-level agent_harness mode:\"learning_curator\" and mode:\"learning_candidate\" remain available for compatibility/detail coverage; route planning prefers personal_ops action:\"briefing\" for daily/calendar briefings, action:\"queue\" for saved review queues, action:\"intake\" for fresh provider-read planning, and action:\"lane\" for connector setup posture before live provider reads or effects; personal_ops action:\"briefing|status|queue|intake|lane|read\" exposes the preferred direct route for a read-only daily Personal Ops briefing plan, read-only saved inbox/calendar review queue, request-to-route Personal Ops planning, lane inspection, and one confirmed read-only inbox/calendar operation, while lower-level modes mode:\"personal_ops_briefing\", mode:\"personal_ops_queue\", mode:\"personal_ops_intake\", mode:\"personal_ops\", mode:\"personal_ops_lane\", and mode:\"run_personal_ops_read\" remain available for harness inspection; Personal Ops surfaces Agent-owned note, routine, schedule-receipt, delivery, task, and reminder live records plus honest email/calendar connector readiness, classified MCP read/write tool hints, schema-derived inbox/calendar operation records with required fields/sample inputs/schema routes/confirmation flags/fresh-read routes, inbox triage/draft and calendar agenda/conflict workflow cards with prerequisites/inspect routes/send-edit boundaries, ordered connector-read/local-compose/setup-repair/confirmed-effect execution plans, confirmed read-only MCP inbox/calendar execution with required-field checks, bounded redacted output, normalized review cards, structured next-route packets for lane/queue inspection, confirmed refresh, saved artifact reopen, local draft/reminder work, and confirmed-effect boundaries, optional saved redacted review-card artifacts, saved review artifacts resurfaced as redacted inbox thread/calendar event queue records, certified provider-effect receipts for inbox/calendar/task/reminder outcomes, fresh daemon/SDK provider-backed inbox/calendar/task/reminder queue records with durable ids, labels, redacted snippets/notes, agenda windows, conflict signals, due times, cadence, delivery targets, source paths, freshness status, schema/version/publication/publisher/provenance/receipt evidence, read-only inspect routes, and confirmed follow-up boundaries only for published provider routes, and a unified read-only queue view with artifact inspect routes, freshness status, confirmed refresh routes when a matching read connector is ready, local draft/reminder follow-up routes, certified provider-effect boundaries, task workflow cards for visible work-plan review and connected-host task review, reminder workflow cards for confirmed reminders and connected schedule review, and matching MCP connector setup routes; agent_harness exposes confirmed workspace editor routes when the user starts from visible workspace forms. agent_harness mode:\"learning_auto_promote\" exposes the confirmed autonomous learning auto-promotion pass to the model.",
156
156
  "safetyBoundary": "VIBE.md, project context files, and local libraries reject secret-looking content; blocked/truncated personality or context files are surfaced instead of silently applied; --include-vibe refuses blocked VIBE.md files; and destructive or cross-surface promotion actions stay confirmation-gated.",
157
157
  "releaseEvidence": "Readiness evidence cites local registries and CLI/runtime commands, with package policy requiring model-visible local action docs."
158
158
  }
@@ -167,7 +167,7 @@
167
167
  "quality": {
168
168
  "capabilityCoverage": "Covers local skills, skill bundles, routines, learned behavior capture, imports, review states, and reusable operator workflows.",
169
169
  "userAccess": "The user can manage skills, routines, bundles, and learned behavior through workspace routes and /skills or /routines commands.",
170
- "modelAccess": "agent_local_registry and memory action:\"curator|candidate\" expose skill/routine actions, editor schemas, selected-record promotion, prompt-active/suppressed/proposal/consolidation planning, local learning review cards, and routine schedule prefill to the model; lower-level agent_harness mode:\"learning_curator\" and mode:\"learning_candidate\" remain available for compatibility/detail coverage; agent_harness mode:\"propose_skill_drafts\" exposes a confirmed pass that drafts up to 3 skills from recent learning candidates, requiring confirm:true and explicitUserRequest, and returns proposed/skipped counts with skill ids.",
170
+ "modelAccess": "agent_local_registry and memory action:\"curator|candidate\" expose skill/routine actions, editor schemas, selected-record promotion, prompt-active/suppressed/proposal/consolidation planning, local learning review cards, and routine schedule prefill to the model; lower-level agent_harness mode:\"learning_curator\" and mode:\"learning_candidate\" remain available for compatibility/detail coverage; agent_harness mode:\"propose_skill_drafts\" exposes a confirmed pass that drafts up to 3 skills from recent learning candidates, requiring confirm:true and explicitUserRequest, and returns proposed/skipped counts with skill ids. agent_harness mode:\"learning_auto_promote\" exposes the confirmed autonomous learning auto-promotion pass to the model.",
171
171
  "safetyBoundary": "Behavior capture remains local until the user explicitly promotes, exports, schedules, or deletes a reviewed record.",
172
172
  "releaseEvidence": "Readiness evidence cites registry and command sources, and release docs describe local behavior library ownership."
173
173
  }
@@ -242,7 +242,7 @@
242
242
  "quality": {
243
243
  "capabilityCoverage": "Covers channel readiness, ordered per-channel setup guide, account posture, setup schema guidance, allowlist policy, live status/doctor checks, read-only channel triage, redacted confirmed-send receipt history, and one-message confirmed delivery across configured channel surfaces.",
244
244
  "userAccess": "The user can inspect setup progress, channel readiness, triage blockers/retries/messages/routes, recent redacted send receipts, and send channel messages through Agent Workspace -> Channels, /channels guide, /channels triage, /channels deliveries, and /channels mirrors.",
245
- "modelAccess": "Route planning prefers channels action:\"setup\" for channel setup wording, action:\"triage\" for blocker/retry/error wording, action:\"deliveries\" for delivery history wording, and action:\"channel\" or status search for send/notify wording before confirmed external delivery; channels action:\"status|channel|setup|triage|deliveries\" exposes structured readiness, current setup guide step, daemon delivery attempts, visible surface messages, route bindings, redacted receipt history, delivery posture, risk labels, safe config-key names, allowlist/status/doctor routes, and explicit test-send boundary; lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\" remain available for compatibility/detail coverage; agent_channel_send exposes the exact confirmed send route and returns the receipt id when the Agent shell path is available.",
245
+ "modelAccess": "Route planning prefers channels action:\"setup\" for channel setup wording, action:\"triage\" for blocker/retry/error wording, action:\"deliveries\" for delivery history wording, and action:\"channel\" or status search for send/notify wording before confirmed external delivery; channels action:\"status|channel|setup|triage|deliveries\" exposes structured readiness, current setup guide step, daemon delivery attempts, visible surface messages, route bindings, redacted receipt history, delivery posture, risk labels, safe config-key names, allowlist/status/doctor routes, and explicit test-send boundary; lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\" remain available for compatibility/detail coverage; agent_channel_send exposes the exact confirmed send route and returns the receipt id when the Agent shell path is available. The unified-inbox and channel draft/routing surfaces also expose agent_harness mode:\"unified_inbox\", mode:\"channel_drafts\", mode:\"channel_draft_save\", mode:\"channel_draft_send\", mode:\"channel_routing\", mode:\"channel_routing_assign\", and mode:\"channel_routing_remove\" to the model with confirmation-gated sends.",
246
246
  "safetyBoundary": "Channel sends require explicit target and confirmation, the setup guide and receipt ledger are read-only, receipts redact webhook/link addresses and bound/redact message previews, does not infer routes from chat, and does not expose secret values.",
247
247
  "releaseEvidence": "Readiness evidence cites channel runtime/tool/docs sources, and live certification requires real external channel outcome coverage before release."
248
248
  }
@@ -437,7 +437,7 @@
437
437
  "quality": {
438
438
  "capabilityCoverage": "Covers core external messaging routes, delivery records, ingress/egress, account lifecycle, directory lookup, target resolution, render policy, and adapter contracts through connected-host ownership.",
439
439
  "userAccess": "The user sees channel readiness, redacted confirmed-send receipts, and confirmed sends in Agent while connected-host owns adapter lifecycle and route execution.",
440
- "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\", connected-host capability, channel workspace, slash-command, and settings metadata expose channel operation boundaries to the model.",
440
+ "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\", connected-host capability, channel workspace, slash-command, and settings metadata expose channel operation boundaries to the model. The unified-inbox and channel draft/routing surfaces also expose agent_harness mode:\"unified_inbox\", mode:\"channel_drafts\", mode:\"channel_draft_save\", mode:\"channel_draft_send\", mode:\"channel_routing\", mode:\"channel_routing_assign\", and mode:\"channel_routing_remove\" to the model with confirmation-gated sends.",
441
441
  "safetyBoundary": "Agent never owns host lifecycle or secret-bearing adapters; it routes exact confirmed delivery through public connected-host contracts.",
442
442
  "releaseEvidence": "Readiness evidence cites connected-host evidence alias and Agent channel docs, with live outcome certification covering real delivery before release."
443
443
  }
@@ -452,7 +452,7 @@
452
452
  "quality": {
453
453
  "capabilityCoverage": "Covers durable jobs, runs, schedules, watchers, deliveries, route bindings, and service lifecycle through connected-host runtime ownership, including published watcher create/list/run/start/stop operator methods, Agent-certified watcher receipt outcomes, Agent-visible handoff requirements for durable watcher run/source records, and Agent consumption of published watcher run/source read models.",
454
454
  "userAccess": "The user can inspect automation state, route trigger setup through autonomy intake, and run narrow confirmed actions from Agent without managing host services.",
455
- "modelAccess": "schedule action:\"list|run|pause|resume|delete\", autonomy action:\"intake|queue|item\", agent_operator_briefing, agent_operator_action, agent_operator_method, host action:\"methods|method|capability\", lower-level agent_harness mode:\"operator_methods|operator_method\", and connected-host capability reports expose automation status/action/watcher boundaries, post-action schedule nextRoutes, certified watcher receipts, source-owned watcher run/source evidence requirements, and published watcher run/source read models to the model.",
455
+ "modelAccess": "schedule action:\"list|run|pause|resume|delete\", autonomy action:\"intake|queue|item\", agent_operator_briefing, agent_operator_action, agent_operator_method, host action:\"methods|method|capability\", lower-level agent_harness mode:\"operator_methods|operator_method\", and connected-host capability reports expose automation status/action/watcher boundaries, post-action schedule nextRoutes, certified watcher receipts, source-owned watcher run/source evidence requirements, and published watcher run/source read models to the model. Remote orchestration also exposes agent_harness mode:\"remote_snapshot\", mode:\"remote_peers\", mode:\"remote_work\", mode:\"remote_pair_requests\", mode:\"remote_pair_approve\", mode:\"remote_pair_reject\", mode:\"remote_peers_invoke\", and mode:\"remote_work_cancel\" to the model with confirmation-gated peer/work control.",
456
456
  "safetyBoundary": "Agent does not create broad host automation definitions or lifecycle operations from chat; watcher creation stays an admin confirmed operator method with trusted source/scope and explicit user request, and receipt certification uses only returned daemon fields.",
457
457
  "releaseEvidence": "Readiness evidence cites connected-host automation inventory and Agent tools docs, with live certification covering external automation outcomes."
458
458
  }
@@ -557,7 +557,7 @@
557
557
  "quality": {
558
558
  "capabilityCoverage": "Covers the long-tail channel matrix through connected-host surface unions, plugin registry, delivery strategies, setup schemas, and operator channel routes.",
559
559
  "userAccess": "The user sees all configured channel readiness, recent redacted delivery receipts, and exact send paths through Agent channel surfaces.",
560
- "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\", channel workspace, command, settings, and connected-host capability reports expose the channel matrix to the model.",
560
+ "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\", channel workspace, command, settings, and connected-host capability reports expose the channel matrix to the model. The unified-inbox and channel draft/routing surfaces also expose agent_harness mode:\"unified_inbox\", mode:\"channel_drafts\", mode:\"channel_draft_save\", mode:\"channel_draft_send\", mode:\"channel_routing\", mode:\"channel_routing_assign\", and mode:\"channel_routing_remove\" to the model with confirmation-gated sends.",
561
561
  "safetyBoundary": "Agent keeps adapter setup and secret-bearing lifecycle in connected-host ownership while confirmed sends stay exact-target.",
562
562
  "releaseEvidence": "Readiness evidence cites connected-host long-tail channel inventory and Agent channel docs, with live outcome certification covering external sends."
563
563
  }
@@ -572,7 +572,7 @@
572
572
  "quality": {
573
573
  "capabilityCoverage": "Covers phone-call style conversation or notification delivery as a channel-class surface through connected-host telephony adapters and routed delivery.",
574
574
  "userAccess": "The user can inspect telephony readiness and send via channel/reminder/routine delivery routes when configured.",
575
- "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, agent_reminder_schedule, and lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\" plus connected-host metadata expose telephony as a routed channel surface to the model.",
575
+ "modelAccess": "channels action:\"status|channel|setup|triage|deliveries\", agent_channel_send, agent_reminder_schedule, and lower-level agent_harness mode:\"channels\", mode:\"channel\", mode:\"channel_setup_guide\", mode:\"channel_triage\", and mode:\"channel_deliveries\" plus connected-host metadata expose telephony as a routed channel surface to the model. The unified-inbox and channel draft/routing surfaces also expose agent_harness mode:\"unified_inbox\", mode:\"channel_drafts\", mode:\"channel_draft_save\", mode:\"channel_draft_send\", mode:\"channel_routing\", mode:\"channel_routing_assign\", and mode:\"channel_routing_remove\" to the model with confirmation-gated sends.",
576
576
  "safetyBoundary": "Agent does not own telephony adapter credentials or service lifecycle and uses explicit target/confirmation rules for delivery.",
577
577
  "releaseEvidence": "Readiness evidence cites connected-host telephony inventory and Agent channel/reminder/routine sources."
578
578
  }
@@ -0,0 +1,431 @@
1
+ /**
2
+ * channel-draft.ts
3
+ *
4
+ * Agent-side outbox + draft model for channel messages.
5
+ *
6
+ * Provides:
7
+ * - `ChannelDraft` — a composed-but-not-yet-sent message
8
+ * - `saveDraft` — persist a draft to the local JSON store
9
+ * - `listDrafts` — read all drafts
10
+ * - `getDraft` — read one draft by id
11
+ * - `deleteDraft` — remove a draft
12
+ * - `queueDraftToSend` — promote a draft to a confirmed send input, consuming it
13
+ *
14
+ * Sends always route through the EXISTING `deliverAgentChannelMessage` path
15
+ * (which wraps `ChannelDeliveryRouter.deliver` via the agent_channel_send confirm
16
+ * pattern). No new SDK contract is introduced.
17
+ *
18
+ * Persistence: JSON file at {agentRoot}/channels/drafts.json via ShellPathService.
19
+ *
20
+ * SEAM — a future daemon `channels.drafts.*` operator method could mirror this
21
+ * store server-side for multi-surface sync. The local model is the source of
22
+ * truth until that contract is published.
23
+ */
24
+
25
+ import { createHash, randomUUID } from 'node:crypto';
26
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
27
+ import { dirname } from 'node:path';
28
+ import type { ShellPathService } from '@/runtime/index.ts';
29
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
30
+ import type { AgentChannelDeliveryInput } from './channel-delivery.ts';
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Types
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export type ChannelDraftStatus = 'draft' | 'queued' | 'sent' | 'failed';
37
+
38
+ export interface ChannelDraft {
39
+ readonly version: 1;
40
+ readonly id: string;
41
+ readonly createdAt: string;
42
+ readonly updatedAt: string;
43
+ readonly status: ChannelDraftStatus;
44
+ /** Human-readable subject / title. */
45
+ readonly title?: string;
46
+ /** Message body. */
47
+ readonly message: string;
48
+ /** Surface target shorthand: e.g. "slack:ops" */
49
+ readonly channel?: string;
50
+ /** Named route id. */
51
+ readonly route?: string;
52
+ /** Webhook URL (stored redacted-safe — callers must redact before persisting). */
53
+ readonly webhook?: string;
54
+ /** Link address. */
55
+ readonly link?: string;
56
+ /** Optional tags for grouping drafts. */
57
+ readonly tags?: readonly string[];
58
+ /** If the draft was sent, the delivery response id. */
59
+ readonly sentResponseId?: string;
60
+ /** If the draft failed to send, the error message. */
61
+ readonly sendError?: string;
62
+ }
63
+
64
+ export interface ChannelDraftSnapshot {
65
+ readonly path: string;
66
+ readonly exists: boolean;
67
+ readonly drafts: readonly ChannelDraft[];
68
+ readonly parseError?: string;
69
+ }
70
+
71
+ export interface ChannelDraftSaveResult {
72
+ readonly draft: ChannelDraft;
73
+ readonly path: string;
74
+ }
75
+
76
+ export interface ChannelDraftQueueResult {
77
+ /** The draft id that was promoted. */
78
+ readonly draftId: string;
79
+ /** The delivery input ready to pass to deliverAgentChannelMessage. */
80
+ readonly deliveryInput: AgentChannelDeliveryInput;
81
+ /** The updated draft (status: 'queued'). */
82
+ readonly draft: ChannelDraft;
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Internal file model
87
+ // ---------------------------------------------------------------------------
88
+
89
+ interface ChannelDraftFile {
90
+ readonly version: 1;
91
+ readonly drafts: readonly ChannelDraft[];
92
+ }
93
+
94
+ type ShellPaths = Pick<ShellPathService, 'resolveUserPath'>;
95
+
96
+ const DRAFT_VERSION = 1 as const;
97
+ const DRAFT_FILE_VERSION = 1 as const;
98
+ const DRAFT_LIMIT = 200;
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Path
102
+ // ---------------------------------------------------------------------------
103
+
104
+ export function channelDraftFilePath(shellPaths: ShellPaths): string {
105
+ return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'channels', 'drafts.json');
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Parsing
110
+ // ---------------------------------------------------------------------------
111
+
112
+ function isRecord(value: unknown): value is Record<string, unknown> {
113
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
114
+ }
115
+
116
+ function readString(value: unknown): string {
117
+ return typeof value === 'string' ? value.trim() : '';
118
+ }
119
+
120
+ function readOptString(value: unknown): string | undefined {
121
+ const s = readString(value);
122
+ return s || undefined;
123
+ }
124
+
125
+ function parseDraftStatus(value: unknown): ChannelDraftStatus {
126
+ if (value === 'queued' || value === 'sent' || value === 'failed') return value;
127
+ return 'draft';
128
+ }
129
+
130
+ function parseTags(value: unknown): readonly string[] | undefined {
131
+ if (!Array.isArray(value)) return undefined;
132
+ const tags = value.map(readString).filter(Boolean);
133
+ return tags.length > 0 ? tags : undefined;
134
+ }
135
+
136
+ function parseDraft(value: unknown): ChannelDraft | null {
137
+ if (!isRecord(value) || value.version !== DRAFT_VERSION) return null;
138
+ const id = readString(value.id);
139
+ const createdAt = readString(value.createdAt);
140
+ const updatedAt = readString(value.updatedAt);
141
+ const message = readString(value.message);
142
+ if (!id || !createdAt || !updatedAt || !message) return null;
143
+ if (Number.isNaN(Date.parse(createdAt)) || Number.isNaN(Date.parse(updatedAt))) return null;
144
+ return {
145
+ version: DRAFT_VERSION,
146
+ id,
147
+ createdAt,
148
+ updatedAt,
149
+ status: parseDraftStatus(value.status),
150
+ message,
151
+ ...( readOptString(value.title) ? { title: readOptString(value.title) } : {}),
152
+ ...( readOptString(value.channel) ? { channel: readOptString(value.channel) } : {}),
153
+ ...( readOptString(value.route) ? { route: readOptString(value.route) } : {}),
154
+ ...( readOptString(value.webhook) ? { webhook: readOptString(value.webhook) } : {}),
155
+ ...( readOptString(value.link) ? { link: readOptString(value.link) } : {}),
156
+ ...(parseTags(value.tags) ? { tags: parseTags(value.tags) } : {}),
157
+ ...( readOptString(value.sentResponseId) ? { sentResponseId: readOptString(value.sentResponseId) } : {}),
158
+ ...( readOptString(value.sendError) ? { sendError: readOptString(value.sendError) } : {}),
159
+ };
160
+ }
161
+
162
+ function parseDraftFile(raw: unknown): ChannelDraftFile {
163
+ if (!isRecord(raw)) return { version: DRAFT_FILE_VERSION, drafts: [] };
164
+ const drafts = Array.isArray(raw.drafts)
165
+ ? raw.drafts.map(parseDraft).filter((d): d is ChannelDraft => d !== null)
166
+ : [];
167
+ return { version: DRAFT_FILE_VERSION, drafts };
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Read / write helpers
172
+ // ---------------------------------------------------------------------------
173
+
174
+ export function readChannelDrafts(shellPaths: ShellPaths): ChannelDraftSnapshot {
175
+ const path = channelDraftFilePath(shellPaths);
176
+ if (!existsSync(path)) return { path, exists: false, drafts: [] };
177
+ try {
178
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
179
+ return { path, exists: true, drafts: parseDraftFile(parsed).drafts };
180
+ } catch (error) {
181
+ return {
182
+ path,
183
+ exists: true,
184
+ drafts: [],
185
+ parseError: error instanceof Error ? error.message : String(error),
186
+ };
187
+ }
188
+ }
189
+
190
+ function writeDrafts(path: string, drafts: readonly ChannelDraft[]): void {
191
+ const file: ChannelDraftFile = { version: DRAFT_FILE_VERSION, drafts: drafts.slice(0, DRAFT_LIMIT) };
192
+ mkdirSync(dirname(path), { recursive: true });
193
+ const tempPath = `${path}.tmp`;
194
+ writeFileSync(tempPath, `${JSON.stringify(file, null, 2)}\n`, 'utf-8');
195
+ renameSync(tempPath, path);
196
+ }
197
+
198
+ function generateDraftId(): string {
199
+ const ts = new Date().toISOString().replace(/[^0-9]/g, '').slice(0, 14);
200
+ return `draft-${ts}-${randomUUID().slice(0, 8)}`;
201
+ }
202
+
203
+ function draftDigest(message: string): string {
204
+ return createHash('sha256').update(message).digest('hex').slice(0, 12);
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Public API
209
+ // ---------------------------------------------------------------------------
210
+
211
+ /**
212
+ * Save a new draft or overwrite an existing draft by id.
213
+ *
214
+ * If `id` is provided in `input` and a draft with that id exists, it is
215
+ * updated in-place (updatedAt refreshed). Otherwise a new draft is created.
216
+ */
217
+ export function saveDraft(
218
+ shellPaths: ShellPaths,
219
+ input: {
220
+ readonly id?: string;
221
+ readonly title?: string;
222
+ readonly message: string;
223
+ readonly channel?: string;
224
+ readonly route?: string;
225
+ readonly webhook?: string;
226
+ readonly link?: string;
227
+ readonly tags?: readonly string[];
228
+ readonly status?: ChannelDraftStatus;
229
+ },
230
+ ): ChannelDraftSaveResult {
231
+ const message = input.message.trim();
232
+ if (!message) throw new Error('Draft message is required.');
233
+
234
+ const snapshot = readChannelDrafts(shellPaths);
235
+ const path = channelDraftFilePath(shellPaths);
236
+ const now = new Date().toISOString();
237
+
238
+ // Try to find existing draft to update
239
+ const existingIndex = input.id ? snapshot.drafts.findIndex((d) => d.id === input.id) : -1;
240
+ const existing = existingIndex >= 0 ? snapshot.drafts[existingIndex] : null;
241
+
242
+ const draft: ChannelDraft = {
243
+ version: DRAFT_VERSION,
244
+ id: existing?.id ?? generateDraftId(),
245
+ createdAt: existing?.createdAt ?? now,
246
+ updatedAt: now,
247
+ status: input.status ?? existing?.status ?? 'draft',
248
+ message,
249
+ ...(input.title?.trim() ? { title: input.title.trim() } : {}),
250
+ ...(input.channel?.trim() ? { channel: input.channel.trim() } : {}),
251
+ ...(input.route?.trim() ? { route: input.route.trim() } : {}),
252
+ ...(input.webhook?.trim() ? { webhook: input.webhook.trim() } : {}),
253
+ ...(input.link?.trim() ? { link: input.link.trim() } : {}),
254
+ ...(input.tags && input.tags.length > 0 ? { tags: input.tags } : {}),
255
+ };
256
+
257
+ let updatedDrafts: readonly ChannelDraft[];
258
+ if (existingIndex >= 0) {
259
+ updatedDrafts = snapshot.drafts.map((d, i) => (i === existingIndex ? draft : d));
260
+ } else {
261
+ // Prepend new draft (most recent first)
262
+ updatedDrafts = [draft, ...snapshot.drafts];
263
+ }
264
+
265
+ writeDrafts(path, updatedDrafts);
266
+ return { draft, path };
267
+ }
268
+
269
+ /** List all drafts, optionally filtered by status. */
270
+ export function listDrafts(
271
+ shellPaths: ShellPaths,
272
+ options: { readonly status?: ChannelDraftStatus; readonly limit?: number } = {},
273
+ ): ChannelDraftSnapshot {
274
+ const snapshot = readChannelDrafts(shellPaths);
275
+ let drafts = snapshot.drafts;
276
+ if (options.status) {
277
+ drafts = drafts.filter((d) => d.status === options.status);
278
+ }
279
+ if (typeof options.limit === 'number' && options.limit > 0) {
280
+ drafts = drafts.slice(0, options.limit);
281
+ }
282
+ return { ...snapshot, drafts };
283
+ }
284
+
285
+ /** Get a single draft by id. Returns null if not found. */
286
+ export function getDraft(shellPaths: ShellPaths, draftId: string): ChannelDraft | null {
287
+ const snapshot = readChannelDrafts(shellPaths);
288
+ return snapshot.drafts.find((d) => d.id === draftId) ?? null;
289
+ }
290
+
291
+ /** Delete a draft by id. Returns true if deleted, false if not found. */
292
+ export function deleteDraft(shellPaths: ShellPaths, draftId: string): boolean {
293
+ const snapshot = readChannelDrafts(shellPaths);
294
+ const before = snapshot.drafts.length;
295
+ const updated = snapshot.drafts.filter((d) => d.id !== draftId);
296
+ if (updated.length === before) return false;
297
+ writeDrafts(channelDraftFilePath(shellPaths), updated);
298
+ return true;
299
+ }
300
+
301
+ /**
302
+ * Promote a draft to 'queued' status and return an AgentChannelDeliveryInput
303
+ * ready to pass to `deliverAgentChannelMessage`.
304
+ *
305
+ * The draft is updated to status 'queued'. The caller is responsible for
306
+ * calling `deliverAgentChannelMessage` and then `markDraftSent` or
307
+ * `markDraftFailed` to record the outcome.
308
+ *
309
+ * This routes through the EXISTING confirmed send path — no new SDK contract.
310
+ */
311
+ export function queueDraftToSend(
312
+ shellPaths: ShellPaths,
313
+ draftId: string,
314
+ ): ChannelDraftQueueResult {
315
+ const draft = getDraft(shellPaths, draftId);
316
+ if (!draft) throw new Error(`Draft not found: ${draftId}`);
317
+ if (draft.status === 'sent') throw new Error(`Draft ${draftId} is already sent.`);
318
+
319
+ const deliveryInput: AgentChannelDeliveryInput = {
320
+ message: draft.message,
321
+ ...(draft.title ? { title: draft.title } : {}),
322
+ ...(draft.channel ? { channel: draft.channel } : {}),
323
+ ...(draft.route ? { route: draft.route } : {}),
324
+ ...(draft.webhook ? { webhook: draft.webhook } : {}),
325
+ ...(draft.link ? { link: draft.link } : {}),
326
+ };
327
+
328
+ const queuedDraft = saveDraft(shellPaths, {
329
+ id: draft.id,
330
+ ...deliveryInput,
331
+ ...(draft.title ? { title: draft.title } : {}),
332
+ ...(draft.tags ? { tags: draft.tags } : {}),
333
+ status: 'queued',
334
+ }).draft;
335
+
336
+ return { draftId, deliveryInput, draft: queuedDraft };
337
+ }
338
+
339
+ /**
340
+ * Mark a draft as successfully sent. Records the response id from the delivery.
341
+ */
342
+ export function markDraftSent(
343
+ shellPaths: ShellPaths,
344
+ draftId: string,
345
+ responseId?: string,
346
+ ): ChannelDraft {
347
+ const snapshot = readChannelDrafts(shellPaths);
348
+ const existing = snapshot.drafts.find((d) => d.id === draftId);
349
+ if (!existing) throw new Error(`Draft not found: ${draftId}`);
350
+ const now = new Date().toISOString();
351
+ const patched: ChannelDraft = {
352
+ ...existing,
353
+ updatedAt: now,
354
+ status: 'sent',
355
+ ...(responseId ? { sentResponseId: responseId } : {}),
356
+ };
357
+ const updatedDrafts = snapshot.drafts.map((d) => (d.id === draftId ? patched : d));
358
+ writeDrafts(channelDraftFilePath(shellPaths), updatedDrafts);
359
+ return patched;
360
+ }
361
+
362
+ /**
363
+ * Mark a draft as failed to send. Records the error message.
364
+ */
365
+ export function markDraftFailed(
366
+ shellPaths: ShellPaths,
367
+ draftId: string,
368
+ error: string,
369
+ ): ChannelDraft {
370
+ const snapshot = readChannelDrafts(shellPaths);
371
+ const existing = snapshot.drafts.find((d) => d.id === draftId);
372
+ if (!existing) throw new Error(`Draft not found: ${draftId}`);
373
+ const now = new Date().toISOString();
374
+ const patched: ChannelDraft = {
375
+ ...existing,
376
+ updatedAt: now,
377
+ status: 'failed',
378
+ sendError: error,
379
+ };
380
+ const updatedDrafts = snapshot.drafts.map((d) => (d.id === draftId ? patched : d));
381
+ writeDrafts(channelDraftFilePath(shellPaths), updatedDrafts);
382
+ return patched;
383
+ }
384
+
385
+ /** Format a single draft for human-readable display. */
386
+ export function formatChannelDraft(draft: ChannelDraft): string {
387
+ const targetPart = draft.channel
388
+ ? `channel=${draft.channel}`
389
+ : draft.route
390
+ ? `route=${draft.route}`
391
+ : draft.webhook
392
+ ? 'webhook=[redacted]'
393
+ : draft.link
394
+ ? `link=${draft.link}`
395
+ : 'no-target';
396
+ const digest = draftDigest(draft.message);
397
+ const lines = [
398
+ `draft ${draft.id}`,
399
+ ` status: ${draft.status}`,
400
+ ` target: ${targetPart}`,
401
+ ...(draft.title ? [` title: ${draft.title}`] : []),
402
+ ` message: ${draft.message.length} chars sha256:${digest}`,
403
+ ` created: ${draft.createdAt} updated: ${draft.updatedAt}`,
404
+ ...(draft.tags && draft.tags.length > 0 ? [` tags: ${draft.tags.join(', ')}`] : []),
405
+ ...(draft.sentResponseId ? [` sent response: ${draft.sentResponseId}`] : []),
406
+ ...(draft.sendError ? [` error: ${draft.sendError}`] : []),
407
+ ];
408
+ return lines.join('\n');
409
+ }
410
+
411
+ /** Format the draft list summary. */
412
+ export function formatChannelDraftList(snapshot: ChannelDraftSnapshot): string {
413
+ const lines = [
414
+ 'Channel Drafts',
415
+ ` path: ${snapshot.path}`,
416
+ ` total: ${snapshot.drafts.length}`,
417
+ ` status: ${snapshot.parseError ? 'attention' : snapshot.exists ? 'ready' : 'empty'}`,
418
+ ...(snapshot.parseError ? [` parse error: ${snapshot.parseError}`] : []),
419
+ ' policy: drafts are agent-local; sends require queueDraftToSend + deliverAgentChannelMessage with explicit user confirmation',
420
+ '',
421
+ ];
422
+ if (snapshot.drafts.length === 0) {
423
+ lines.push(' no drafts');
424
+ } else {
425
+ for (const draft of snapshot.drafts) {
426
+ const targetPart = draft.channel ?? draft.route ?? (draft.webhook ? 'webhook=[redacted]' : null) ?? draft.link ?? 'no-target';
427
+ lines.push(` [${draft.status}] ${draft.id} target=${targetPart} ${draft.message.slice(0, 60)}${draft.message.length > 60 ? '...' : ''}`);
428
+ }
429
+ }
430
+ return lines.join('\n');
431
+ }