Skip to content

Commit b92b287

Browse files
committed
increase the lease time from 30sec to 20min
1 parent 0a6b18e commit b92b287

6 files changed

Lines changed: 121 additions & 3 deletions

File tree

‎lua/copilot_agent/config.lua‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ local defaults = {
3939
healthcheck_path = '/healthz',
4040
startup_timeout_ms = 15000,
4141
startup_poll_interval_ms = 250,
42+
client_heartbeat_interval_ms = 10000,
4243
},
4344
chat = {
4445
split = 'botright vnew',
@@ -185,6 +186,7 @@ local state = {
185186
service_process_pid = nil,
186187
client_id = nil,
187188
client_registered_base_url = nil,
189+
client_heartbeat_timer = nil,
188190
client_lease_registered = false,
189191
service_launch_info = nil, -- human-readable launch mode/command shown in chat header
190192
service_starting = false,

‎lua/copilot_agent/init.lua‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ function M.setup(opts)
147147
state.service_job_id = nil
148148
state.service_process_pid = nil
149149
state.client_registered_base_url = nil
150+
if state.client_heartbeat_timer then
151+
pcall(function()
152+
state.client_heartbeat_timer:stop()
153+
state.client_heartbeat_timer:close()
154+
end)
155+
end
156+
state.client_heartbeat_timer = nil
150157
state.service_starting = false
151158
state.service_addr_known = false
152159
state.pending_service_callbacks = {}

‎lua/copilot_agent/service.lua‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ local SERVICE_OUTPUT_HISTORY_LIMIT = 20 -- Keep only the most recent startup lin
2121
local CONTROL_REQUEST_TIMEOUT_SECONDS = '2' -- Bound local control-socket curl calls so stale sockets cannot hang startup or exit indefinitely.
2222
local SAVED_ADDR_PROBE_TIMEOUT_SECONDS = '1' -- Quick probe timeout when validating persisted service addresses.
2323
local STALE_ADDR_FAILURE_THRESHOLD = 3 -- After this many consecutive health-check failures on the same discovered address, force a fresh restart.
24+
local DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS = 10000 -- Refresh client registration periodically so detached idle shutdown never races active chat use.
2425
local DEFAULT_SERVICE_LOG_PATH = vim.fn.stdpath('state') .. '/copilot-agent-service.log'
2526
local DEFAULT_CLIENT_ID_LENGTH = 32
2627

@@ -648,6 +649,63 @@ local function interval_settings()
648649
return timeout_ms, interval_ms
649650
end
650651

652+
local function client_heartbeat_interval_ms()
653+
local interval = tonumber(state.config.service.client_heartbeat_interval_ms)
654+
if interval == nil then
655+
interval = DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS
656+
end
657+
return math.max(0, interval)
658+
end
659+
660+
local function close_client_heartbeat_timer()
661+
local timer = state.client_heartbeat_timer
662+
if timer == nil then
663+
return
664+
end
665+
pcall(function()
666+
timer:stop()
667+
timer:close()
668+
end)
669+
state.client_heartbeat_timer = nil
670+
end
671+
672+
local function start_client_heartbeat_timer()
673+
if state.shutting_down then
674+
return
675+
end
676+
677+
local interval_ms = client_heartbeat_interval_ms()
678+
if interval_ms <= 0 then
679+
return
680+
end
681+
682+
if state.client_heartbeat_timer == nil then
683+
state.client_heartbeat_timer = uv.new_timer()
684+
end
685+
local timer = state.client_heartbeat_timer
686+
if timer == nil then
687+
return
688+
end
689+
690+
timer:stop()
691+
timer:start(
692+
interval_ms,
693+
interval_ms,
694+
vim.schedule_wrap(function()
695+
if state.shutting_down then
696+
return
697+
end
698+
if type(state.client_registered_base_url) ~= 'string' or state.client_registered_base_url == '' then
699+
return
700+
end
701+
local registered, register_err = M.register_client()
702+
if not registered and register_err then
703+
log('client heartbeat renewal failed: ' .. tostring(register_err), vim.log.levels.DEBUG)
704+
end
705+
end)
706+
)
707+
end
708+
651709
write_text_file = function(path, content)
652710
local ok, err = pcall(function()
653711
local f = assert(io.open(path, 'w'))
@@ -958,6 +1016,7 @@ function M.register_client()
9581016

9591017
if state.client_registered_base_url == base_url then
9601018
log(string.format('client already registered client_id=%s base_url=%s', M.client_id(), base_url), vim.log.levels.DEBUG)
1019+
start_client_heartbeat_timer()
9611020
return true
9621021
end
9631022

@@ -990,11 +1049,13 @@ function M.register_client()
9901049
end
9911050

9921051
state.client_registered_base_url = base_url
1052+
start_client_heartbeat_timer()
9931053
log(string.format('registering client succeeded client_id=%s base_url=%s', M.client_id(), base_url), vim.log.levels.DEBUG)
9941054
return true
9951055
end
9961056

9971057
function M.unregister_client()
1058+
close_client_heartbeat_timer()
9981059
local base_url = type(state.client_registered_base_url) == 'string' and state.client_registered_base_url or current_service_base_url()
9991060
if base_url == '' then
10001061
log('unregister_client skipped: no registered base_url', vim.log.levels.DEBUG)

‎server/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ Service log lines are prefixed with `[INFO]`, `[WARN]`, or `[ERROR]` when
5151

5252
When `-client-lease-dir` is configured, detached-service shutdown is delayed by
5353
the lease watcher grace window. Current defaults (from `server/main.go`) are:
54-
`clientLeasePollInterval = 500ms` and `clientLeaseEmptyGrace = 30s` (i.e. the
55-
lease directory must stay empty for 30 seconds before shutdown).
54+
`clientLeasePollInterval = 500ms` and `clientLeaseEmptyGrace = 5m` (i.e. the
55+
lease directory must stay empty for five minutes before shutdown).
5656

5757
---
5858

‎server/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const (
4545
httpShutdownTimeout = 5 * time.Second // Allow in-flight HTTP requests a brief grace period during service shutdown.
4646
sseKeepAliveInterval = 15 * time.Second // Keep reverse proxies and clients from treating idle event streams as dead.
4747
clientLeasePollInterval = 500 * time.Millisecond
48-
clientLeaseEmptyGrace = 30 * time.Second
48+
clientLeaseEmptyGrace = 10 * time.Minute
4949
sseSubscriberBufferSize = 256 // Absorb bursts of session events (tool calls can produce 100+ events rapidly).
5050
asyncResultChannelSize = 1 // Each pending prompt/permission only needs to hold a single terminal response.
5151
permissionRequestIDPrefix = "perm"

‎tests/integration/setup_spec.lua‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10400,6 +10400,7 @@ describe('chat input behavior', function()
1040010400

1040110401
it('opens an apply_patch hover preview for multi-file activity changes', function()
1040210402
local events = require('copilot_agent.events')
10403+
local render = require('copilot_agent.render')
1040310404
agent.state.session_id = 'session-123'
1040410405
agent.state.entries = {}
1040510406
agent.state.entry_row_index = {}
@@ -10494,10 +10495,57 @@ describe('chat input behavior', function()
1049410495
assert_true(joined:find('*** Update File: lua/copilot_agent/activity_diff.lua', 1, true) ~= nil)
1049510496
assert_true(joined:find('*** Update File: lua/copilot_agent/chat.lua', 1, true) ~= nil)
1049610497
assert_true(joined:find('@@', 1, true) ~= nil)
10498+
local focused = render.focus_activity_hover_preview(agent.state.chat_winid)
10499+
assert_true(focused)
10500+
assert_true(vim.api.nvim_win_is_valid(vim.api.nvim_get_current_win()))
10501+
preview_winid = agent.state.activity_hover_winid
10502+
vim.wait(250)
10503+
assert_true(vim.api.nvim_win_is_valid(preview_winid))
10504+
10505+
vim.api.nvim_set_current_win(agent.state.chat_winid)
10506+
vim.wait(250)
10507+
assert_true(preview_winid == nil or not vim.api.nvim_win_is_valid(preview_winid))
1049710508
vim.wait(180)
1049810509
assert_true(preview_winid == nil or not vim.api.nvim_win_is_valid(preview_winid))
1049910510
end)
1050010511

10512+
it('keeps the hover preview open while it has focus', function()
10513+
local render = require('copilot_agent.render')
10514+
agent.state.config.chat.activity_view = 'hover'
10515+
agent.state.config.chat.activity_hover_timeout_ms = 1000
10516+
agent.state.entries = {
10517+
{
10518+
kind = 'activity',
10519+
content = 'Used edit',
10520+
activity_items = {
10521+
{
10522+
kind = 'tool',
10523+
tool_name = 'bash',
10524+
summary = 'Used bash',
10525+
},
10526+
},
10527+
},
10528+
}
10529+
agent.open_chat()
10530+
agent.state.entry_row_index = { [0] = 1 }
10531+
vim.api.nvim_win_set_cursor(agent.state.chat_winid, { 1, 0 })
10532+
10533+
local opened = render.refresh_activity_hover_preview(agent.state.chat_winid)
10534+
assert_true(opened)
10535+
local hover_winid = agent.state.activity_hover_winid
10536+
assert_true(hover_winid ~= nil and vim.api.nvim_win_is_valid(hover_winid))
10537+
10538+
local focused = render.focus_activity_hover_preview(agent.state.chat_winid)
10539+
assert_true(focused)
10540+
hover_winid = agent.state.activity_hover_winid
10541+
vim.wait(250)
10542+
assert_true(vim.api.nvim_win_is_valid(hover_winid))
10543+
10544+
vim.api.nvim_set_current_win(agent.state.chat_winid)
10545+
vim.wait(1250)
10546+
assert_true(hover_winid == nil or not vim.api.nvim_win_is_valid(hover_winid))
10547+
end)
10548+
1050110549
it('opens a hover diff preview for single-file activity changes', function()
1050210550
local events = require('copilot_agent.events')
1050310551
agent.state.session_id = 'session-123'

0 commit comments

Comments
 (0)