@phenx-inc/ctlsurf 0.3.6 → 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 -43
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +67 -43
- package/package.json +1 -1
- package/src/main/timeTracker.ts +74 -45
package/out/main/index.js
CHANGED
|
@@ -10160,55 +10160,68 @@ class TimeTracker {
|
|
|
10160
10160
|
if (this.sessions.has(tabId)) {
|
|
10161
10161
|
await this.endSession(tabId);
|
|
10162
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;
|
|
10163
10201
|
try {
|
|
10164
|
-
const blockId = await this.ensureDatastore(cwd);
|
|
10165
|
-
if (!blockId)
|
|
10166
|
-
log$2(`No "${AGENT_DATASTORE_PAGE_TITLE}" page found for ${cwd} — tracking disabled for this session`);
|
|
10167
|
-
return;
|
|
10168
|
-
}
|
|
10169
|
-
const startedAt = Date.now();
|
|
10170
|
-
const sessionUuid = require$$1.randomUUID();
|
|
10202
|
+
const blockId = await this.ensureDatastore(s.cwd);
|
|
10203
|
+
if (!blockId) return false;
|
|
10171
10204
|
const row = await this.api.addRow(blockId, {
|
|
10172
|
-
Started: formatStarted(startedAt),
|
|
10173
|
-
"Active Time":
|
|
10174
|
-
"Last Updated": new Date(
|
|
10175
|
-
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,
|
|
10176
10209
|
Worker: os.hostname(),
|
|
10177
|
-
Session: sessionUuid,
|
|
10210
|
+
Session: s.sessionUuid,
|
|
10178
10211
|
Notes: ""
|
|
10179
10212
|
});
|
|
10180
10213
|
const rowId = row?.id;
|
|
10181
10214
|
if (!rowId) {
|
|
10182
|
-
log$2(
|
|
10183
|
-
return;
|
|
10215
|
+
log$2(`addRow returned no id for tab=${tabId}; will retry on next checkpoint`);
|
|
10216
|
+
return false;
|
|
10184
10217
|
}
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
agentName,
|
|
10190
|
-
idleTimeoutMin,
|
|
10191
|
-
startedAt,
|
|
10192
|
-
lastActivity: startedAt,
|
|
10193
|
-
activeMs: 0,
|
|
10194
|
-
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
|
|
10195
|
-
firstCheckpointTimer: null,
|
|
10196
|
-
checkpointTimer: null,
|
|
10197
|
-
ended: false
|
|
10198
|
-
};
|
|
10199
|
-
state.firstCheckpointTimer = setTimeout(() => {
|
|
10200
|
-
void this.checkpoint(tabId);
|
|
10201
|
-
const live = this.sessions.get(tabId);
|
|
10202
|
-
if (live && !live.ended) {
|
|
10203
|
-
live.checkpointTimer = setInterval(() => {
|
|
10204
|
-
void this.checkpoint(tabId);
|
|
10205
|
-
}, CHECKPOINT_INTERVAL_MS);
|
|
10206
|
-
}
|
|
10207
|
-
}, FIRST_CHECKPOINT_DELAY_MS);
|
|
10208
|
-
this.sessions.set(tabId, state);
|
|
10209
|
-
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;
|
|
10210
10222
|
} catch (err) {
|
|
10211
|
-
log$2(`
|
|
10223
|
+
log$2(`tryResolve failed for tab=${tabId}: ${err?.message || err}`);
|
|
10224
|
+
return false;
|
|
10212
10225
|
}
|
|
10213
10226
|
}
|
|
10214
10227
|
isTracking(tabId) {
|
|
@@ -10248,14 +10261,21 @@ class TimeTracker {
|
|
|
10248
10261
|
async endSession(tabId) {
|
|
10249
10262
|
const s = this.sessions.get(tabId);
|
|
10250
10263
|
if (!s || s.ended) return;
|
|
10251
|
-
s.ended = true;
|
|
10252
10264
|
if (s.firstCheckpointTimer) clearTimeout(s.firstCheckpointTimer);
|
|
10253
10265
|
if (s.checkpointTimer) clearInterval(s.checkpointTimer);
|
|
10254
10266
|
try {
|
|
10255
|
-
|
|
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
|
+
}
|
|
10256
10275
|
} catch (err) {
|
|
10257
10276
|
log$2(`endSession write failed: ${err?.message || err}`);
|
|
10258
10277
|
}
|
|
10278
|
+
s.ended = true;
|
|
10259
10279
|
this.sessions.delete(tabId);
|
|
10260
10280
|
}
|
|
10261
10281
|
async endAll() {
|
|
@@ -10265,13 +10285,16 @@ class TimeTracker {
|
|
|
10265
10285
|
async checkpoint(tabId) {
|
|
10266
10286
|
const s = this.sessions.get(tabId);
|
|
10267
10287
|
if (!s || s.ended) return;
|
|
10288
|
+
if (!s.blockId || !s.rowId) {
|
|
10289
|
+
if (!await this.tryResolve(tabId)) return;
|
|
10290
|
+
}
|
|
10268
10291
|
try {
|
|
10269
10292
|
await this.writeRow(s, Date.now());
|
|
10270
10293
|
} catch (err) {
|
|
10271
10294
|
log$2(`checkpoint failed: ${err?.message || err}; retrying in 2s`);
|
|
10272
10295
|
setTimeout(() => {
|
|
10273
10296
|
const live = this.sessions.get(tabId);
|
|
10274
|
-
if (!live || live.ended) return;
|
|
10297
|
+
if (!live || live.ended || !live.blockId || !live.rowId) return;
|
|
10275
10298
|
this.writeRow(live, Date.now()).catch((err2) => {
|
|
10276
10299
|
log$2(`checkpoint retry failed: ${err2?.message || err2}`);
|
|
10277
10300
|
});
|
|
@@ -10279,6 +10302,7 @@ class TimeTracker {
|
|
|
10279
10302
|
}
|
|
10280
10303
|
}
|
|
10281
10304
|
async writeRow(s, _endTimeMs) {
|
|
10305
|
+
if (!s.blockId || !s.rowId) return;
|
|
10282
10306
|
const activeMin = Math.round(s.activeMs / 6e4);
|
|
10283
10307
|
await this.api.updateRow(s.blockId, s.rowId, {
|
|
10284
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,
|