@ctrl-spc/cli 1.1.13 → 1.1.15
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/dist/controlRequests.js +18 -8
- package/dist/daemon.js +9 -3
- package/dist/spawner.js +3 -1
- package/package.json +1 -1
package/dist/controlRequests.js
CHANGED
|
@@ -11,6 +11,14 @@ export function makeControlRequestProcessor(deps) {
|
|
|
11
11
|
.limit(1);
|
|
12
12
|
return data?.[0]?.value ?? null;
|
|
13
13
|
}
|
|
14
|
+
async function resolveMachineLabel() {
|
|
15
|
+
const { data } = await deps.session.client
|
|
16
|
+
.from('devices')
|
|
17
|
+
.select('label')
|
|
18
|
+
.eq('id', deps.session.machineId)
|
|
19
|
+
.single();
|
|
20
|
+
return (data?.label ?? 'This machine').trim() || 'This machine';
|
|
21
|
+
}
|
|
14
22
|
async function mark(row, patch) {
|
|
15
23
|
await deps.session.client.from('agent_control_requests').update(patch).eq('id', row.id);
|
|
16
24
|
}
|
|
@@ -44,7 +52,7 @@ export function makeControlRequestProcessor(deps) {
|
|
|
44
52
|
async function findSession(row) {
|
|
45
53
|
let query = deps.session.client
|
|
46
54
|
.from('sessions')
|
|
47
|
-
.select('id, project_id, agent_type, model, effort, permission_mode, allowed_tools')
|
|
55
|
+
.select('id, project_id, device_id, agent_type, model, effort, permission_mode, allowed_tools')
|
|
48
56
|
.eq('project_id', row.project_id)
|
|
49
57
|
.is('ended_at', null);
|
|
50
58
|
if (row.session_id)
|
|
@@ -66,8 +74,7 @@ export function makeControlRequestProcessor(deps) {
|
|
|
66
74
|
throw new Error(`Message insert failed: ${error.message}`);
|
|
67
75
|
return data?.id ?? null;
|
|
68
76
|
}
|
|
69
|
-
async function processPing(row) {
|
|
70
|
-
const session = await findSession(row);
|
|
77
|
+
async function processPing(row, session) {
|
|
71
78
|
if (!session) {
|
|
72
79
|
await mark(row, completedPatch('failed', 'No active agent session was found.'));
|
|
73
80
|
return;
|
|
@@ -80,15 +87,15 @@ export function makeControlRequestProcessor(deps) {
|
|
|
80
87
|
const { cmd, args } = assembleCommand(session, PING_PROMPT);
|
|
81
88
|
const handle = deps.spawnAgent(cmd, args, cwd);
|
|
82
89
|
const stdout = await collectStdout(handle);
|
|
83
|
-
const
|
|
90
|
+
const machineLabel = await resolveMachineLabel();
|
|
91
|
+
const responseId = await insertMessage(row, session.id, 'agent', 'ping', `${machineLabel} responded: ${cleanAgentText(stdout)}`);
|
|
84
92
|
await mark(row, { ...completedPatch('succeeded'), response_message_id: responseId });
|
|
85
93
|
}
|
|
86
|
-
async function processStop(row) {
|
|
94
|
+
async function processStop(row, session) {
|
|
87
95
|
const owned = row.session_id
|
|
88
96
|
? deps.ownedSessions.get(row.session_id)
|
|
89
97
|
: [...deps.ownedSessions.values()].find((entry) => entry.projectId === row.project_id);
|
|
90
98
|
if (!owned) {
|
|
91
|
-
const session = await findSession(row);
|
|
92
99
|
if (session) {
|
|
93
100
|
const endedAt = new Date(deps.now()).toISOString();
|
|
94
101
|
await deps.session.client.from('sessions').update({
|
|
@@ -117,13 +124,16 @@ export function makeControlRequestProcessor(deps) {
|
|
|
117
124
|
await mark(row, { status: 'succeeded', completed_at: endedAt });
|
|
118
125
|
}
|
|
119
126
|
async function processOne(row) {
|
|
127
|
+
const session = await findSession(row);
|
|
128
|
+
if (session?.device_id && session.device_id !== deps.session.machineId)
|
|
129
|
+
return;
|
|
120
130
|
await mark(row, { status: 'processing' });
|
|
121
131
|
if (row.action === 'ping') {
|
|
122
|
-
await processPing(row);
|
|
132
|
+
await processPing(row, session);
|
|
123
133
|
return;
|
|
124
134
|
}
|
|
125
135
|
if (row.action === 'stop') {
|
|
126
|
-
await processStop(row);
|
|
136
|
+
await processStop(row, session);
|
|
127
137
|
return;
|
|
128
138
|
}
|
|
129
139
|
await mark(row, completedPatch('failed', 'This control request is not supported by this version of the daemon.'));
|
package/dist/daemon.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loadMachineState } from './session.js';
|
|
|
3
3
|
import { makeSpawner, nodeSpawnAgent } from './spawner.js';
|
|
4
4
|
import { makeControlRequestProcessor } from './controlRequests.js';
|
|
5
5
|
export const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
6
|
+
export const WORK_SCAN_INTERVAL_MS = 2_000;
|
|
6
7
|
const SESSION_RETRY_INTERVAL_MS = 5_000;
|
|
7
8
|
export async function heartbeatOnce(session, deps) {
|
|
8
9
|
try {
|
|
@@ -113,14 +114,19 @@ export async function runDaemon() {
|
|
|
113
114
|
process.once('SIGTERM', () => void shutdown());
|
|
114
115
|
session = await heartbeatOnce(session, deps);
|
|
115
116
|
await markRecoveredSessionsDisconnected(session, () => Date.now(), deps.log);
|
|
117
|
+
let lastHeartbeatAt = Date.now();
|
|
116
118
|
let spawner = makeSpawner({ session, spawnAgent: nodeSpawnAgent, ownedSessions, now: () => Date.now(), log: deps.log });
|
|
117
119
|
let controls = makeControlRequestProcessor({ session, spawnAgent: nodeSpawnAgent, ownedSessions, now: () => Date.now(), log: deps.log });
|
|
118
120
|
await runSpawnScan(spawner, deps.log);
|
|
119
121
|
await runControlScan(controls, deps.log);
|
|
120
122
|
while (true) {
|
|
121
|
-
await sleep(
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
await sleep(WORK_SCAN_INTERVAL_MS);
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
if (now - lastHeartbeatAt >= HEARTBEAT_INTERVAL_MS) {
|
|
126
|
+
session = await heartbeatOnce(session, deps);
|
|
127
|
+
await touchOwnedSessions(session, ownedSessions, () => now, deps.log);
|
|
128
|
+
lastHeartbeatAt = now;
|
|
129
|
+
}
|
|
124
130
|
spawner = makeSpawner({ session, spawnAgent: nodeSpawnAgent, ownedSessions, now: () => Date.now(), log: deps.log });
|
|
125
131
|
controls = makeControlRequestProcessor({ session, spawnAgent: nodeSpawnAgent, ownedSessions, now: () => Date.now(), log: deps.log });
|
|
126
132
|
await runSpawnScan(spawner, deps.log);
|
package/dist/spawner.js
CHANGED
|
@@ -77,6 +77,8 @@ export function makeSpawner(deps) {
|
|
|
77
77
|
async function processOne(row) {
|
|
78
78
|
if (!isRemote(row.agent_type))
|
|
79
79
|
return; // manual types are never auto-fulfilled
|
|
80
|
+
if (row.target_device_id && row.target_device_id !== deps.session.machineId)
|
|
81
|
+
return;
|
|
80
82
|
const last = lastAttempt.get(row.id);
|
|
81
83
|
if (last !== undefined && deps.now() - last < backoffMs)
|
|
82
84
|
return;
|
|
@@ -102,7 +104,7 @@ export function makeSpawner(deps) {
|
|
|
102
104
|
async function processPending() {
|
|
103
105
|
const { data, error } = await deps.session.client
|
|
104
106
|
.from('spawn_requests')
|
|
105
|
-
.select('id, project_id, agent_type, model, effort, permission_mode, allowed_tools')
|
|
107
|
+
.select('id, project_id, target_device_id, agent_type, model, effort, permission_mode, allowed_tools')
|
|
106
108
|
.eq('status', 'pending');
|
|
107
109
|
if (error) {
|
|
108
110
|
deps.log(`spawn_requests scan failed: ${error.message}`);
|