@phenx-inc/ctlsurf 0.3.5 → 0.3.7
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/out/headless/index.mjs +67 -49
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +67 -49
- package/package.json +1 -1
- package/src/main/timeTracker.ts +74 -45
- package/src/main/workerWs.ts +0 -6
package/out/main/index.js
CHANGED
|
@@ -9949,12 +9949,6 @@ class WorkerWsClient {
|
|
|
9949
9949
|
this.send({ type: "terminal_resize", cols, rows });
|
|
9950
9950
|
}
|
|
9951
9951
|
sendChatLog(entry) {
|
|
9952
|
-
log$3("[worker-ws] chat_log DEBUG", {
|
|
9953
|
-
type: entry.type,
|
|
9954
|
-
chars: entry.content.length,
|
|
9955
|
-
lines: entry.content.split("\n").length,
|
|
9956
|
-
preview: entry.content.slice(0, 500)
|
|
9957
|
-
});
|
|
9958
9952
|
this.send({ type: "chat_log", entry });
|
|
9959
9953
|
}
|
|
9960
9954
|
doConnect() {
|
|
@@ -10166,55 +10160,68 @@ class TimeTracker {
|
|
|
10166
10160
|
if (this.sessions.has(tabId)) {
|
|
10167
10161
|
await this.endSession(tabId);
|
|
10168
10162
|
}
|
|
10163
|
+
const startedAt = Date.now();
|
|
10164
|
+
const state = {
|
|
10165
|
+
blockId: null,
|
|
10166
|
+
rowId: null,
|
|
10167
|
+
sessionUuid: require$$1.randomUUID(),
|
|
10168
|
+
cwd,
|
|
10169
|
+
agentName,
|
|
10170
|
+
idleTimeoutMin,
|
|
10171
|
+
startedAt,
|
|
10172
|
+
lastActivity: startedAt,
|
|
10173
|
+
activeMs: 0,
|
|
10174
|
+
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
|
|
10175
|
+
firstCheckpointTimer: null,
|
|
10176
|
+
checkpointTimer: null,
|
|
10177
|
+
ended: false
|
|
10178
|
+
};
|
|
10179
|
+
this.sessions.set(tabId, state);
|
|
10180
|
+
await this.tryResolve(tabId);
|
|
10181
|
+
state.firstCheckpointTimer = setTimeout(() => {
|
|
10182
|
+
void this.checkpoint(tabId);
|
|
10183
|
+
const live = this.sessions.get(tabId);
|
|
10184
|
+
if (live && !live.ended) {
|
|
10185
|
+
live.checkpointTimer = setInterval(() => {
|
|
10186
|
+
void this.checkpoint(tabId);
|
|
10187
|
+
}, CHECKPOINT_INTERVAL_MS);
|
|
10188
|
+
}
|
|
10189
|
+
}, FIRST_CHECKPOINT_DELAY_MS);
|
|
10190
|
+
const pending = !state.blockId || !state.rowId;
|
|
10191
|
+
log$2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}${pending ? " (pending datastore — will retry on each checkpoint)" : ""}`);
|
|
10192
|
+
}
|
|
10193
|
+
/** Attempts to locate (or create) the datastore + add the session row.
|
|
10194
|
+
* Returns true once the session is resolved (blockId + rowId set).
|
|
10195
|
+
* Safe to call repeatedly: re-running while pending will keep retrying;
|
|
10196
|
+
* once resolved it's a no-op. */
|
|
10197
|
+
async tryResolve(tabId) {
|
|
10198
|
+
const s = this.sessions.get(tabId);
|
|
10199
|
+
if (!s) return false;
|
|
10200
|
+
if (s.blockId && s.rowId) return true;
|
|
10169
10201
|
try {
|
|
10170
|
-
const blockId = await this.ensureDatastore(cwd);
|
|
10171
|
-
if (!blockId)
|
|
10172
|
-
log$2(`No "${AGENT_DATASTORE_PAGE_TITLE}" page found for ${cwd} — tracking disabled for this session`);
|
|
10173
|
-
return;
|
|
10174
|
-
}
|
|
10175
|
-
const startedAt = Date.now();
|
|
10176
|
-
const sessionUuid = require$$1.randomUUID();
|
|
10202
|
+
const blockId = await this.ensureDatastore(s.cwd);
|
|
10203
|
+
if (!blockId) return false;
|
|
10177
10204
|
const row = await this.api.addRow(blockId, {
|
|
10178
|
-
Started: formatStarted(startedAt),
|
|
10179
|
-
"Active Time":
|
|
10180
|
-
"Last Updated": new Date(
|
|
10181
|
-
Agent: agentName,
|
|
10205
|
+
Started: formatStarted(s.startedAt),
|
|
10206
|
+
"Active Time": Math.round(s.activeMs / 6e4),
|
|
10207
|
+
"Last Updated": (/* @__PURE__ */ new Date()).toISOString(),
|
|
10208
|
+
Agent: s.agentName,
|
|
10182
10209
|
Worker: os.hostname(),
|
|
10183
|
-
Session: sessionUuid,
|
|
10210
|
+
Session: s.sessionUuid,
|
|
10184
10211
|
Notes: ""
|
|
10185
10212
|
});
|
|
10186
10213
|
const rowId = row?.id;
|
|
10187
10214
|
if (!rowId) {
|
|
10188
|
-
log$2(
|
|
10189
|
-
return;
|
|
10215
|
+
log$2(`addRow returned no id for tab=${tabId}; will retry on next checkpoint`);
|
|
10216
|
+
return false;
|
|
10190
10217
|
}
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
agentName,
|
|
10196
|
-
idleTimeoutMin,
|
|
10197
|
-
startedAt,
|
|
10198
|
-
lastActivity: startedAt,
|
|
10199
|
-
activeMs: 0,
|
|
10200
|
-
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
|
|
10201
|
-
firstCheckpointTimer: null,
|
|
10202
|
-
checkpointTimer: null,
|
|
10203
|
-
ended: false
|
|
10204
|
-
};
|
|
10205
|
-
state.firstCheckpointTimer = setTimeout(() => {
|
|
10206
|
-
void this.checkpoint(tabId);
|
|
10207
|
-
const live = this.sessions.get(tabId);
|
|
10208
|
-
if (live && !live.ended) {
|
|
10209
|
-
live.checkpointTimer = setInterval(() => {
|
|
10210
|
-
void this.checkpoint(tabId);
|
|
10211
|
-
}, CHECKPOINT_INTERVAL_MS);
|
|
10212
|
-
}
|
|
10213
|
-
}, FIRST_CHECKPOINT_DELAY_MS);
|
|
10214
|
-
this.sessions.set(tabId, state);
|
|
10215
|
-
log$2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}`);
|
|
10218
|
+
s.blockId = blockId;
|
|
10219
|
+
s.rowId = rowId;
|
|
10220
|
+
log$2(`Resolved datastore for tab=${tabId} (cwd=${s.cwd})`);
|
|
10221
|
+
return true;
|
|
10216
10222
|
} catch (err) {
|
|
10217
|
-
log$2(`
|
|
10223
|
+
log$2(`tryResolve failed for tab=${tabId}: ${err?.message || err}`);
|
|
10224
|
+
return false;
|
|
10218
10225
|
}
|
|
10219
10226
|
}
|
|
10220
10227
|
isTracking(tabId) {
|
|
@@ -10254,14 +10261,21 @@ class TimeTracker {
|
|
|
10254
10261
|
async endSession(tabId) {
|
|
10255
10262
|
const s = this.sessions.get(tabId);
|
|
10256
10263
|
if (!s || s.ended) return;
|
|
10257
|
-
s.ended = true;
|
|
10258
10264
|
if (s.firstCheckpointTimer) clearTimeout(s.firstCheckpointTimer);
|
|
10259
10265
|
if (s.checkpointTimer) clearInterval(s.checkpointTimer);
|
|
10260
10266
|
try {
|
|
10261
|
-
|
|
10267
|
+
if (!s.blockId || !s.rowId) {
|
|
10268
|
+
await this.tryResolve(tabId);
|
|
10269
|
+
}
|
|
10270
|
+
if (s.blockId && s.rowId) {
|
|
10271
|
+
await this.writeRow(s, Date.now());
|
|
10272
|
+
} else {
|
|
10273
|
+
log$2(`endSession for tab=${tabId}: never resolved datastore; ${Math.round(s.activeMs / 6e4)}min not recorded`);
|
|
10274
|
+
}
|
|
10262
10275
|
} catch (err) {
|
|
10263
10276
|
log$2(`endSession write failed: ${err?.message || err}`);
|
|
10264
10277
|
}
|
|
10278
|
+
s.ended = true;
|
|
10265
10279
|
this.sessions.delete(tabId);
|
|
10266
10280
|
}
|
|
10267
10281
|
async endAll() {
|
|
@@ -10271,13 +10285,16 @@ class TimeTracker {
|
|
|
10271
10285
|
async checkpoint(tabId) {
|
|
10272
10286
|
const s = this.sessions.get(tabId);
|
|
10273
10287
|
if (!s || s.ended) return;
|
|
10288
|
+
if (!s.blockId || !s.rowId) {
|
|
10289
|
+
if (!await this.tryResolve(tabId)) return;
|
|
10290
|
+
}
|
|
10274
10291
|
try {
|
|
10275
10292
|
await this.writeRow(s, Date.now());
|
|
10276
10293
|
} catch (err) {
|
|
10277
10294
|
log$2(`checkpoint failed: ${err?.message || err}; retrying in 2s`);
|
|
10278
10295
|
setTimeout(() => {
|
|
10279
10296
|
const live = this.sessions.get(tabId);
|
|
10280
|
-
if (!live || live.ended) return;
|
|
10297
|
+
if (!live || live.ended || !live.blockId || !live.rowId) return;
|
|
10281
10298
|
this.writeRow(live, Date.now()).catch((err2) => {
|
|
10282
10299
|
log$2(`checkpoint retry failed: ${err2?.message || err2}`);
|
|
10283
10300
|
});
|
|
@@ -10285,6 +10302,7 @@ class TimeTracker {
|
|
|
10285
10302
|
}
|
|
10286
10303
|
}
|
|
10287
10304
|
async writeRow(s, _endTimeMs) {
|
|
10305
|
+
if (!s.blockId || !s.rowId) return;
|
|
10288
10306
|
const activeMin = Math.round(s.activeMs / 6e4);
|
|
10289
10307
|
await this.api.updateRow(s.blockId, s.rowId, {
|
|
10290
10308
|
"Active Time": activeMin,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phenx-inc/ctlsurf",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5
5
|
"main": "out/main/index.js",
|
|
6
6
|
"bin": {
|
package/src/main/timeTracker.ts
CHANGED
|
@@ -30,8 +30,9 @@ function formatStarted(ms: number): string {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
interface SessionState {
|
|
33
|
-
blockId: string
|
|
34
|
-
rowId: string
|
|
33
|
+
blockId: string | null
|
|
34
|
+
rowId: string | null
|
|
35
|
+
sessionUuid: string
|
|
35
36
|
cwd: string
|
|
36
37
|
agentName: string
|
|
37
38
|
idleTimeoutMin: number
|
|
@@ -83,55 +84,72 @@ export class TimeTracker {
|
|
|
83
84
|
if (this.sessions.has(tabId)) {
|
|
84
85
|
await this.endSession(tabId)
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
const startedAt = Date.now()
|
|
88
|
+
const state: SessionState = {
|
|
89
|
+
blockId: null,
|
|
90
|
+
rowId: null,
|
|
91
|
+
sessionUuid: randomUUID(),
|
|
92
|
+
cwd,
|
|
93
|
+
agentName,
|
|
94
|
+
idleTimeoutMin,
|
|
95
|
+
startedAt,
|
|
96
|
+
lastActivity: startedAt,
|
|
97
|
+
activeMs: 0,
|
|
98
|
+
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1000,
|
|
99
|
+
firstCheckpointTimer: null,
|
|
100
|
+
checkpointTimer: null,
|
|
101
|
+
ended: false,
|
|
102
|
+
}
|
|
103
|
+
this.sessions.set(tabId, state)
|
|
104
|
+
|
|
105
|
+
await this.tryResolve(tabId)
|
|
106
|
+
|
|
107
|
+
state.firstCheckpointTimer = setTimeout(() => {
|
|
108
|
+
void this.checkpoint(tabId)
|
|
109
|
+
const live = this.sessions.get(tabId)
|
|
110
|
+
if (live && !live.ended) {
|
|
111
|
+
live.checkpointTimer = setInterval(() => {
|
|
112
|
+
void this.checkpoint(tabId)
|
|
113
|
+
}, CHECKPOINT_INTERVAL_MS)
|
|
91
114
|
}
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
}, FIRST_CHECKPOINT_DELAY_MS)
|
|
116
|
+
|
|
117
|
+
const pending = !state.blockId || !state.rowId
|
|
118
|
+
log(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}${pending ? ' (pending datastore — will retry on each checkpoint)' : ''}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Attempts to locate (or create) the datastore + add the session row.
|
|
122
|
+
* Returns true once the session is resolved (blockId + rowId set).
|
|
123
|
+
* Safe to call repeatedly: re-running while pending will keep retrying;
|
|
124
|
+
* once resolved it's a no-op. */
|
|
125
|
+
private async tryResolve(tabId: string): Promise<boolean> {
|
|
126
|
+
const s = this.sessions.get(tabId)
|
|
127
|
+
if (!s) return false
|
|
128
|
+
if (s.blockId && s.rowId) return true
|
|
129
|
+
try {
|
|
130
|
+
const blockId = await this.ensureDatastore(s.cwd)
|
|
131
|
+
if (!blockId) return false
|
|
94
132
|
const row = await this.api.addRow(blockId, {
|
|
95
|
-
Started: formatStarted(startedAt),
|
|
96
|
-
'Active Time':
|
|
97
|
-
'Last Updated': new Date(
|
|
98
|
-
Agent: agentName,
|
|
133
|
+
Started: formatStarted(s.startedAt),
|
|
134
|
+
'Active Time': Math.round(s.activeMs / 60000),
|
|
135
|
+
'Last Updated': new Date().toISOString(),
|
|
136
|
+
Agent: s.agentName,
|
|
99
137
|
Worker: os.hostname(),
|
|
100
|
-
Session: sessionUuid,
|
|
138
|
+
Session: s.sessionUuid,
|
|
101
139
|
Notes: '',
|
|
102
140
|
})
|
|
103
141
|
const rowId = row?.id
|
|
104
142
|
if (!rowId) {
|
|
105
|
-
log(
|
|
106
|
-
return
|
|
143
|
+
log(`addRow returned no id for tab=${tabId}; will retry on next checkpoint`)
|
|
144
|
+
return false
|
|
107
145
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
agentName,
|
|
113
|
-
idleTimeoutMin,
|
|
114
|
-
startedAt,
|
|
115
|
-
lastActivity: startedAt,
|
|
116
|
-
activeMs: 0,
|
|
117
|
-
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1000,
|
|
118
|
-
firstCheckpointTimer: null,
|
|
119
|
-
checkpointTimer: null,
|
|
120
|
-
ended: false,
|
|
121
|
-
}
|
|
122
|
-
state.firstCheckpointTimer = setTimeout(() => {
|
|
123
|
-
void this.checkpoint(tabId)
|
|
124
|
-
const live = this.sessions.get(tabId)
|
|
125
|
-
if (live && !live.ended) {
|
|
126
|
-
live.checkpointTimer = setInterval(() => {
|
|
127
|
-
void this.checkpoint(tabId)
|
|
128
|
-
}, CHECKPOINT_INTERVAL_MS)
|
|
129
|
-
}
|
|
130
|
-
}, FIRST_CHECKPOINT_DELAY_MS)
|
|
131
|
-
this.sessions.set(tabId, state)
|
|
132
|
-
log(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}`)
|
|
146
|
+
s.blockId = blockId
|
|
147
|
+
s.rowId = rowId
|
|
148
|
+
log(`Resolved datastore for tab=${tabId} (cwd=${s.cwd})`)
|
|
149
|
+
return true
|
|
133
150
|
} catch (err: any) {
|
|
134
|
-
log(`
|
|
151
|
+
log(`tryResolve failed for tab=${tabId}: ${err?.message || err}`)
|
|
152
|
+
return false
|
|
135
153
|
}
|
|
136
154
|
}
|
|
137
155
|
|
|
@@ -175,14 +193,21 @@ export class TimeTracker {
|
|
|
175
193
|
async endSession(tabId: string): Promise<void> {
|
|
176
194
|
const s = this.sessions.get(tabId)
|
|
177
195
|
if (!s || s.ended) return
|
|
178
|
-
s.ended = true
|
|
179
196
|
if (s.firstCheckpointTimer) clearTimeout(s.firstCheckpointTimer)
|
|
180
197
|
if (s.checkpointTimer) clearInterval(s.checkpointTimer)
|
|
181
198
|
try {
|
|
182
|
-
|
|
199
|
+
if (!s.blockId || !s.rowId) {
|
|
200
|
+
await this.tryResolve(tabId)
|
|
201
|
+
}
|
|
202
|
+
if (s.blockId && s.rowId) {
|
|
203
|
+
await this.writeRow(s, Date.now())
|
|
204
|
+
} else {
|
|
205
|
+
log(`endSession for tab=${tabId}: never resolved datastore; ${Math.round(s.activeMs / 60000)}min not recorded`)
|
|
206
|
+
}
|
|
183
207
|
} catch (err: any) {
|
|
184
208
|
log(`endSession write failed: ${err?.message || err}`)
|
|
185
209
|
}
|
|
210
|
+
s.ended = true
|
|
186
211
|
this.sessions.delete(tabId)
|
|
187
212
|
}
|
|
188
213
|
|
|
@@ -194,13 +219,16 @@ export class TimeTracker {
|
|
|
194
219
|
private async checkpoint(tabId: string): Promise<void> {
|
|
195
220
|
const s = this.sessions.get(tabId)
|
|
196
221
|
if (!s || s.ended) return
|
|
222
|
+
if (!s.blockId || !s.rowId) {
|
|
223
|
+
if (!(await this.tryResolve(tabId))) return
|
|
224
|
+
}
|
|
197
225
|
try {
|
|
198
226
|
await this.writeRow(s, Date.now())
|
|
199
227
|
} catch (err: any) {
|
|
200
228
|
log(`checkpoint failed: ${err?.message || err}; retrying in 2s`)
|
|
201
229
|
setTimeout(() => {
|
|
202
230
|
const live = this.sessions.get(tabId)
|
|
203
|
-
if (!live || live.ended) return
|
|
231
|
+
if (!live || live.ended || !live.blockId || !live.rowId) return
|
|
204
232
|
this.writeRow(live, Date.now()).catch((err2: any) => {
|
|
205
233
|
log(`checkpoint retry failed: ${err2?.message || err2}`)
|
|
206
234
|
})
|
|
@@ -209,6 +237,7 @@ export class TimeTracker {
|
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
private async writeRow(s: SessionState, _endTimeMs: number): Promise<void> {
|
|
240
|
+
if (!s.blockId || !s.rowId) return
|
|
212
241
|
const activeMin = Math.round(s.activeMs / 60000)
|
|
213
242
|
await this.api.updateRow(s.blockId, s.rowId, {
|
|
214
243
|
'Active Time': activeMin,
|
package/src/main/workerWs.ts
CHANGED
|
@@ -135,12 +135,6 @@ export class WorkerWsClient {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
sendChatLog(entry: { type: string; content: string; ts?: string }): void {
|
|
138
|
-
log('[worker-ws] chat_log DEBUG', {
|
|
139
|
-
type: entry.type,
|
|
140
|
-
chars: entry.content.length,
|
|
141
|
-
lines: entry.content.split('\n').length,
|
|
142
|
-
preview: entry.content.slice(0, 500),
|
|
143
|
-
})
|
|
144
138
|
this.send({ type: 'chat_log', entry })
|
|
145
139
|
}
|
|
146
140
|
|