Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b9ee1be
Agent collaboration: live event inbox, presence pill, diff flashes, b…
HamptonMakes Aug 7, 2026
124afd0
Voice tier, collaboration docs, and API orphan-thread fix
HamptonMakes Aug 7, 2026
a115038
Attach mode: interrupt a live agent instead of waking a dead one
HamptonMakes Aug 10, 2026
6405d68
Fix ghost presence pills and the pill gap during an agent's first turn
HamptonMakes Aug 10, 2026
3cfc5d4
Stop attached agents from starving the web server; signal-driven wakes
HamptonMakes Aug 10, 2026
d826074
Remove three API frictions found by driving the loop as a real agent
HamptonMakes Aug 10, 2026
bfed549
Add a `watching` state so an idle attached agent stops claiming to work
HamptonMakes Aug 11, 2026
bd99940
Fix ghost agent pills and give attached-but-idle agents their own state
HamptonMakes Aug 11, 2026
b8555d1
Add an authority model for comments and mintable session tokens
HamptonMakes Aug 11, 2026
aa59a48
Keep the session token sticky, and out of the agent's hands entirely
HamptonMakes Aug 12, 2026
f10f71b
Voice commenting: pin dictated comments to the passage they're about
HamptonMakes Aug 12, 2026
6c786ba
Clean up dictated remarks and add hold-Shift-to-talk
HamptonMakes Aug 12, 2026
a52adc8
Record audio and transcribe server-side; fix table-cell anchors
HamptonMakes Aug 13, 2026
f03ff66
Never post a recording nobody spoke into
HamptonMakes Aug 13, 2026
3a753f4
Don't let a dead meter call speech silence; say misses out loud
HamptonMakes Aug 13, 2026
6617285
Open the mic at the keydown, not after the hold is confirmed
HamptonMakes Aug 13, 2026
71caa6e
Resolve spans broken by inline markup instead of falling to the heading
HamptonMakes Aug 13, 2026
8db7ec9
Refuse comments whose anchor doesn't pin, and stop rejecting spans th…
HamptonMakes Aug 13, 2026
ad4947a
Merge remote-tracking branch 'origin/main' into worktree-agent-collab
HamptonMakes Aug 13, 2026
2460e57
Voice comments open themselves, and the excerpt means readable, not i…
HamptonMakes Aug 13, 2026
11eeb70
Excerpt accumulates across the take; open-thread goes through Stimulu…
HamptonMakes Aug 13, 2026
0044e16
One dictation, several comments: "rename both of these" is two placem…
HamptonMakes Aug 13, 2026
368c388
Refuse transcripts the recording had no time to hold
HamptonMakes Aug 13, 2026
8072e6b
Render comment keyboard nav even when the page loads without threads
HamptonMakes Aug 14, 2026
58491e7
Merge main: voice commenting shipped (#175) + library org API (#176)
HamptonMakes Aug 14, 2026
dd59ad2
Interpreter: speak as the speaker, and know where they were reading
HamptonMakes Aug 15, 2026
b8e0b18
Merge remote-tracking branch 'origin/main' into worktree-agent-collab
HamptonMakes Aug 19, 2026
53f51a8
Align the live-agent loop with the merged identity layer
HamptonMakes Aug 19, 2026
afd3cef
coplan-attach: make --timeout able to fire mid-stream
HamptonMakes Aug 19, 2026
6d0be34
The pill says what the server knows: waking, not working
HamptonMakes Aug 19, 2026
79b016e
Wakes go where a path exists; promises go where one was kept
HamptonMakes Aug 20, 2026
7382371
Harden the wake paths against the eleven ways they lied or leaked
HamptonMakes Aug 20, 2026
c46a411
Make the first encounter one curl: served tools, setup branches, brid…
HamptonMakes Aug 21, 2026
b6c12a2
Harden the bridge's config surface and survive mount prefixes
HamptonMakes Aug 21, 2026
e6c69d1
Lead the setup with curl, not with downloaded code
HamptonMakes Aug 21, 2026
49283a2
Merge origin/main (browsable URLs, slideshows, RuboCop CI, resolved-t…
HamptonMakes Aug 24, 2026
e6d74bc
Appease the newly CI-enforced RuboCop across the agent files
HamptonMakes Aug 24, 2026
07cac44
Absorb main's changed-sections struct and browse catchall
HamptonMakes Aug 24, 2026
01cdf1a
Address Codex review: seven confirmed findings
HamptonMakes Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/admin/agent_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ActiveAdmin.register CoPlan::AgentEvent, as: "AgentEvent" do
# Inbox rows are written by the platform and acked by agents; admin is
# for inspection only (a stuck inbox, a delivery question).
actions :index, :show

index do
id_column
column :event_type
column :plan
column :api_token
column :acked_at
column :created_at
end

show do
attributes_table do
row :id
row :event_type
row :plan
row :api_token
row :comment_thread_id
row :comment_id
row :payload
row :acked_at
row :created_at
end
end
end
38 changes: 38 additions & 0 deletions app/admin/agent_sessions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ActiveAdmin.register CoPlan::AgentSession, as: "AgentSession" do
# Read-mostly: sessions are claimed and updated over the API. Destroy
# stays available so an operator can clear a stuck row.
actions :index, :show, :destroy

index do
selectable_column
id_column
column :agent_name
column :plan
column :api_token
column :state
column :state_detail
column :last_activity_at
column :wakes_answered_count
column :wake_failures_count
actions
end

show do
attributes_table do
row :id
row :agent_name
row :plan
row :api_token
row :state
row :state_detail
row :last_activity_at
row :last_transport_at
# wake_secret is deliberately not rendered — it's a credential.
row :wake_url
row :wakes_answered_count
row :wake_failures_count
row :created_at
row :updated_at
end
end
end
8 changes: 8 additions & 0 deletions config/initializers/coplan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
# }
# }

# Wake webhook egress: the engine default refuses URLs whose hosts
# resolve to private/loopback/link-local space (SSRF). Locally that's
# exactly where agents live, and specs use non-resolving example hosts,
# so dev and test allow any well-formed http(s) URL.
if Rails.env.development? || Rails.env.test?
config.wake_url_policy = ->(uri) { true }
end

config.notification_handler = ->(event, payload) {
case event
when :comment_created
Expand Down
6 changes: 3 additions & 3 deletions db/cable_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
t.binary "payload", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "channel_hash", limit: 8, null: false
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
t.index [ "channel" ], name: "index_solid_cable_messages_on_channel"
t.index [ "channel_hash" ], name: "index_solid_cable_messages_on_channel_hash"
t.index [ "created_at" ], name: "index_solid_cable_messages_on_created_at"
end
end
6 changes: 3 additions & 3 deletions db/cache_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
t.datetime "created_at", null: false
t.integer "key_hash", limit: 8, null: false
t.integer "byte_size", limit: 4, null: false
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
t.index [ "byte_size" ], name: "index_solid_cache_entries_on_byte_size"
t.index [ "key_hash", "byte_size" ], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
t.index [ "key_hash" ], name: "index_solid_cache_entries_on_key_hash", unique: true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# This migration comes from co_plan (originally 20260807000000)
class CreateAgentCollaborationTables < ActiveRecord::Migration[8.1]
# Guarded because these objects may already exist on both sides of the
# split this migration heals: databases loaded from schema.rb carry the
# tables (they leaked into the schema via #175's regeneration against a
# dev database), and api_tokens.agent_name ships with the identity
# migration (20260815000000), which runs first on hosts that install
# this one later.
def change
# Durable per-agent event inbox. IDs are UUIDv7 (time-ordered), so the
# id doubles as the pagination cursor: "give me events after <id>".
unless table_exists?(:coplan_agent_events)
create_table :coplan_agent_events, id: { type: :string, limit: 36 } do |t|
t.string :api_token_id, limit: 36, null: false
t.string :plan_id, limit: 36, null: false
t.string :comment_thread_id, limit: 36
t.string :comment_id, limit: 36
t.string :event_type, null: false
t.json :payload
t.datetime :acked_at
t.datetime :created_at, null: false

t.index [ :api_token_id, :id ]
t.index [ :api_token_id, :acked_at ]
t.index :plan_id
end

add_foreign_key :coplan_agent_events, :coplan_api_tokens, column: :api_token_id
add_foreign_key :coplan_agent_events, :coplan_plans, column: :plan_id
end

# One session per (plan, agent token) — Linear-style delegation state
# machine driving the presence pill: pending / active / awaiting_input /
# complete / stale.
unless table_exists?(:coplan_agent_sessions)
create_table :coplan_agent_sessions, id: { type: :string, limit: 36 } do |t|
t.string :plan_id, limit: 36, null: false
t.string :api_token_id, limit: 36, null: false
t.string :agent_name, null: false
t.string :state, null: false, default: "pending"
t.string :state_detail
t.datetime :last_activity_at
t.timestamps

t.index [ :plan_id, :api_token_id ], unique: true
t.index :api_token_id
end

add_foreign_key :coplan_agent_sessions, :coplan_api_tokens, column: :api_token_id
add_foreign_key :coplan_agent_sessions, :coplan_plans, column: :plan_id
end

# Stable display identity for an agent token, instead of the free-text
# per-comment agent_name. Normally added by 20260815000000 already.
unless column_exists?(:coplan_api_tokens, :agent_name)
add_column :coplan_api_tokens, :agent_name, :string
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This migration comes from co_plan (originally 20260820000000)
class AddWakePlumbingToAgentSessions < ActiveRecord::Migration[8.0]
# Guarded like 20260807000000: main's schema.rb has carried leaked
# agent-collab structure before, so never assume a clean slate.
def change
return unless table_exists?(:coplan_agent_sessions)

# Evidence a connection is actually parked on this session's token —
# SSE heartbeats and long-poll parks touch it. Distinct from
# last_activity_at, which tracks the agent/state machine: a held
# socket must not keep a `pending` promise alive forever.
unless column_exists?(:coplan_agent_sessions, :last_transport_at)
add_column :coplan_agent_sessions, :last_transport_at, :datetime
end

# How many wakes this session has demonstrably answered (pending →
# an agent-driven state). Zero means the loop is unproven and the
# pill makes no wake promise.
unless column_exists?(:coplan_agent_sessions, :wakes_answered_count)
add_column :coplan_agent_sessions, :wakes_answered_count, :integer, default: 0, null: false
end

# Webhook wake: a session may register a URL CoPlan POSTs a signed
# "you have inbox items" ping to — the wake path for hosted agents
# that can receive HTTP but can't hold a connection or be resumed.
unless column_exists?(:coplan_agent_sessions, :wake_url)
add_column :coplan_agent_sessions, :wake_url, :string
end

unless column_exists?(:coplan_agent_sessions, :wake_secret)
add_column :coplan_agent_sessions, :wake_secret, :string
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This migration comes from co_plan (originally 20260821000000)
class AddWakeFailuresCountToAgentSessions < ActiveRecord::Migration[8.0]
def change
return unless table_exists?(:coplan_agent_sessions)
return if column_exists?(:coplan_agent_sessions, :wake_failures_count)

# Exhausted wake-webhook delivery runs since the last success; the
# URL is unregistered once this crosses WakeWebhookJob::MAX_EXHAUSTIONS.
add_column :coplan_agent_sessions, :wake_failures_count, :integer, default: 0, null: false
end
end
5 changes: 5 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading