Skip to content

Commit cc95124

Browse files
committed
Fix client hand-off with server
1. use client instead of PID 2. server side couting 3. harding the reboot and retry
1 parent 16c142d commit cc95124

9 files changed

Lines changed: 991 additions & 110 deletions

File tree

lua/copilot_agent/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ local state = {
183183
input_statusline_managed = false, -- true when plugin populated the input window local statusline
184184
service_job_id = nil,
185185
service_process_pid = nil,
186+
client_id = nil,
187+
client_registered_base_url = nil,
188+
client_lease_registered = false,
186189
service_launch_info = nil, -- human-readable launch mode/command shown in chat header
187190
service_starting = false,
188191
shutting_down = false, -- true during VimLeavePre cleanup; suppress reconnect/recovery noise on intentional shutdown

lua/copilot_agent/http.lua

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,20 @@ local is_connection_error = utils.is_connection_error
1717

1818
local M = {}
1919

20-
local function effective_base_url()
20+
local function effective_base_url(base_url_override)
21+
local override = normalize_base_url(base_url_override)
22+
if type(override) == 'string' and override ~= '' then
23+
return override
24+
end
25+
2126
if state.base_url_managed ~= false then
2227
local ok, service = pcall(require, 'copilot_agent.service')
2328
if ok and type(service) == 'table' and type(service.refresh_managed_base_url) == 'function' then
24-
service.refresh_managed_base_url()
29+
if service.refresh_managed_base_url() then
30+
return normalize_base_url(state.config.base_url)
31+
end
2532
end
33+
return ''
2634
end
2735

2836
local base_url = normalize_base_url(state.config.base_url)
@@ -42,20 +50,20 @@ local function effective_base_url()
4250
return base_url
4351
end
4452

45-
function M.build_url(path)
46-
return effective_base_url() .. path
53+
function M.build_url(path, base_url_override)
54+
return effective_base_url(base_url_override) .. path
4755
end
4856

4957
local function has_base_url()
5058
local base_url = effective_base_url()
5159
return type(base_url) == 'string' and base_url ~= ''
5260
end
5361

54-
local function log_http_request(mode, method, path, body)
62+
local function log_http_request(mode, method, path, body, base_url_override)
5563
if not should_log(vim.log.levels.DEBUG) then
5664
return
5765
end
58-
log(string.format('http.%s request method=%s path=%s url=%s body=%s', mode, tostring(method), tostring(path), M.build_url(path), serialize_log_value(body)), vim.log.levels.DEBUG)
66+
log(string.format('http.%s request method=%s path=%s url=%s body=%s', mode, tostring(method), tostring(path), M.build_url(path, base_url_override), serialize_log_value(body)), vim.log.levels.DEBUG)
5967
end
6068

6169
local function log_http_response(mode, method, path, status, exit_code, payload, err)
@@ -132,12 +140,13 @@ function M.encode_json(value)
132140
end
133141

134142
-- Synchronous HTTP request via vim.system / vim.fn.system.
135-
function M.sync_request(method, path, body)
143+
function M.sync_request(method, path, body, opts)
144+
opts = opts or {}
136145
if not M.ensure_curl() then
137146
return nil, 'curl executable not found: ' .. state.config.curl_bin
138147
end
139148

140-
log_http_request('sync', method, path, body)
149+
log_http_request('sync', method, path, body, opts.base_url)
141150
local args = {
142151
state.config.curl_bin,
143152
'-sS',
@@ -147,7 +156,7 @@ function M.sync_request(method, path, body)
147156
'\n%{http_code}',
148157
'-X',
149158
method,
150-
M.build_url(path),
159+
M.build_url(path, opts.base_url),
151160
'-H',
152161
'Accept: application/json',
153162
}
@@ -216,13 +225,14 @@ function M.sync_request(method, path, body)
216225
end
217226

218227
-- Async HTTP request via vim.fn.jobstart.
219-
function M.raw_request(method, path, body, callback)
228+
function M.raw_request(method, path, body, callback, opts)
229+
opts = opts or {}
220230
if not M.ensure_curl() then
221231
callback(nil, 'curl executable not found: ' .. state.config.curl_bin)
222232
return
223233
end
224234

225-
log_http_request('async', method, path, body)
235+
log_http_request('async', method, path, body, opts.base_url)
226236
local stdout = {}
227237
local stderr = {}
228238
local args = {
@@ -234,7 +244,7 @@ function M.raw_request(method, path, body, callback)
234244
'\n%{http_code}',
235245
'-X',
236246
method,
237-
M.build_url(path),
247+
M.build_url(path, opts.base_url),
238248
'-H',
239249
'Accept: application/json',
240250
}
@@ -313,12 +323,13 @@ function M.request(method, path, body, callback, opts)
313323

314324
-- Lazy-require to avoid circular dependency with service.lua.
315325
local service = require('copilot_agent.service')
326+
service.forget_service_addr()
316327
service.ensure_service_running(function(start_err)
317328
if start_err then
318329
callback(nil, start_err, nil)
319330
return
320331
end
321-
M.raw_request(method, path, body, callback)
332+
M.raw_request(method, path, body, callback, opts)
322333
end)
323334
return
324335
end
@@ -331,14 +342,15 @@ function M.request(method, path, body, callback, opts)
331342
)
332343
-- Lazy-require to break http↔service circular dependency.
333344
local service = require('copilot_agent.service')
345+
service.forget_service_addr()
334346
service.ensure_service_running(function(start_err)
335347
if start_err then
336348
log(string.format('http.request retry failed method=%s path=%s startup_error=%s', tostring(method), tostring(path), serialize_log_value(start_err, { max_len = 600 })), vim.log.levels.WARN)
337349
callback(nil, err .. '\n' .. start_err, status)
338350
return
339351
end
340352
log(string.format('http.request retrying method=%s path=%s after service start', tostring(method), tostring(path)), vim.log.levels.DEBUG)
341-
M.raw_request(method, path, body, callback)
353+
M.raw_request(method, path, body, callback, opts)
342354
end)
343355
return
344356
end

lua/copilot_agent/init.lua

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ local cfg = require('copilot_agent.config')
2525
local defaults = cfg.defaults
2626
local state = cfg.state
2727
local notify = cfg.notify
28+
local log = cfg.log
2829
local normalize_base_url = cfg.normalize_base_url
2930

3031
local service = require('copilot_agent.service')
@@ -144,6 +145,14 @@ function M.setup(opts)
144145
end
145146
state.config.base_url = normalize_base_url(state.config.base_url)
146147
state.service_launch_info = describe_service_launch()
148+
state.service_job_id = nil
149+
state.service_process_pid = nil
150+
state.client_registered_base_url = nil
151+
state.service_starting = false
152+
state.service_addr_known = false
153+
state.pending_service_callbacks = {}
154+
state.service_output = {}
155+
state.lsp_client_id = nil
147156
state.shutting_down = false
148157
-- Initialize runtime permission mode from config.
149158
state.permission_mode = state.config.permission_mode or 'interactive'
@@ -263,21 +272,46 @@ function M.setup(opts)
263272
end,
264273
})
265274
events.remember_open_buffer_disk_state()
266-
service.register_client_lease()
267275
-- Clean up clipboard temp files if Neovim exits before they were sent.
268276
vim.api.nvim_create_autocmd('VimLeavePre', {
269277
group = vim.api.nvim_create_augroup('CopilotAgentCleanup', { clear = true }),
270278
callback = function()
279+
log(string.format('VimLeavePre cleanup start pid=%d', vim.fn.getpid()), vim.log.levels.DEBUG)
271280
state.shutting_down = true
272-
events.stop_event_stream()
273-
local ok, logger = pcall(require, 'copilot_agent.log')
274-
if ok and type(logger.flush_pending) == 'function' then
275-
logger.flush_pending()
281+
local ok, err = pcall(events.stop_event_stream)
282+
if ok then
283+
log('VimLeavePre stopped event stream', vim.log.levels.DEBUG)
284+
else
285+
log('VimLeavePre stop_event_stream failed: ' .. tostring(err), vim.log.levels.WARN)
286+
end
287+
local logger_ok, logger = pcall(require, 'copilot_agent.log')
288+
local attachments_ok, attachments_err = pcall(session.discard_pending_attachments)
289+
if attachments_ok then
290+
log('VimLeavePre discarded pending attachments', vim.log.levels.DEBUG)
291+
else
292+
log('VimLeavePre discard_pending_attachments failed: ' .. tostring(attachments_err), vim.log.levels.WARN)
293+
end
294+
local unregister_ok, unregister_err = pcall(service.unregister_client)
295+
if unregister_ok then
296+
log('VimLeavePre unregistered client', vim.log.levels.DEBUG)
297+
else
298+
log('VimLeavePre unregister_client failed: ' .. tostring(unregister_err), vim.log.levels.WARN)
299+
end
300+
local stop_ok, stop_err = pcall(service.stop_service)
301+
if stop_ok then
302+
log('VimLeavePre stop_service completed', vim.log.levels.DEBUG)
303+
else
304+
log('VimLeavePre stop_service failed: ' .. tostring(stop_err), vim.log.levels.WARN)
305+
end
306+
if logger_ok and type(logger.flush_pending) == 'function' then
307+
local flush_ok, flush_err = pcall(logger.flush_pending)
308+
if flush_ok then
309+
log('VimLeavePre flushed pending logs', vim.log.levels.DEBUG)
310+
pcall(logger.flush_pending)
311+
else
312+
log('VimLeavePre flush_pending failed: ' .. tostring(flush_err), vim.log.levels.WARN)
313+
end
276314
end
277-
session.discard_pending_attachments()
278-
service.unregister_client_lease()
279-
service.maybe_shutdown_detached_service_if_last_client({ nonblocking = true })
280-
service.stop_service()
281315
end,
282316
})
283317
vim.schedule(function()
@@ -465,6 +499,7 @@ function M.statusline_reasoning(max_len)
465499
end
466500

467501
function M.status()
502+
service.refresh_managed_base_url()
468503
local snapshot = service.debug_snapshot()
469504
local lines = {
470505
'service: ' .. normalize_base_url(state.config.base_url),
@@ -489,6 +524,7 @@ function M.status()
489524
end
490525

491526
function M.debug_service()
527+
service.refresh_managed_base_url()
492528
local snapshot = service.debug_snapshot()
493529
local lines = {
494530
'Service debug:',

lua/copilot_agent/lsp.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,8 @@ local function lsp_only_command(service_url)
660660
if type(cmd) ~= 'table' or vim.tbl_isempty(cmd) then
661661
return nil
662662
end
663-
return vim.list_extend(cmd, { '-lsp-only', '--service-url', service_url })
663+
vim.list_extend(cmd, { '-lsp-only', '--service-url', service_url })
664+
return cmd
664665
end
665666

666667
function M.paste_clipboard_image()

lua/copilot_agent/render.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3305,6 +3305,7 @@ function M.render_chat()
33053305
state.entry_row_index = {}
33063306

33073307
lines[#lines + 1] = state.config.chat.title
3308+
service.refresh_managed_base_url()
33083309
local service_url = normalize_base_url(state.config.base_url)
33093310
if service_url == '' then
33103311
service_url = '<discovering via control socket>'

0 commit comments

Comments
 (0)