@ccmsg/cli 0.3.1 → 0.3.2
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/package.json +1 -1
- package/src/sessions/harness.ts +49 -21
package/package.json
CHANGED
package/src/sessions/harness.ts
CHANGED
|
@@ -127,6 +127,7 @@ class CodexThreads implements OwnSessions {
|
|
|
127
127
|
* so the watch is what the subscription drives, and no answer waits on it. */
|
|
128
128
|
export class HarnessSessions implements OwnSessions {
|
|
129
129
|
readonly #watch: DirectoryWatch;
|
|
130
|
+
readonly #lastComplete = new Map<string, AgentInfo>();
|
|
130
131
|
|
|
131
132
|
constructor(
|
|
132
133
|
private readonly dir: string,
|
|
@@ -174,16 +175,32 @@ export class HarnessSessions implements OwnSessions {
|
|
|
174
175
|
* uid's own config home (M6) — a syscall or two per session, not a wait. */
|
|
175
176
|
scan(): ReadonlyMap<Sid, AgentInfo> {
|
|
176
177
|
const rows = new Map<Sid, AgentInfo>();
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
const names = this.#watch.names().filter((name) => STATE_FILE.test(name));
|
|
179
|
+
const present = new Set(names);
|
|
180
|
+
for (const name of names) {
|
|
179
181
|
let document: unknown;
|
|
180
182
|
try {
|
|
181
183
|
document = JSON.parse(readFileSync(join(this.dir, name), "utf8"));
|
|
182
184
|
} catch {
|
|
185
|
+
const previous = this.#lastComplete.get(name);
|
|
186
|
+
if (previous !== undefined) rows.set(previous.sid, previous);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const result = toRow(document, this.dir, this.instance);
|
|
190
|
+
if (!result.complete) {
|
|
191
|
+
const previous = this.#lastComplete.get(name);
|
|
192
|
+
if (previous !== undefined) rows.set(previous.sid, previous);
|
|
183
193
|
continue;
|
|
184
194
|
}
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
if (result.row === undefined) {
|
|
196
|
+
this.#lastComplete.delete(name);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
this.#lastComplete.set(name, result.row);
|
|
200
|
+
rows.set(result.row.sid, result.row);
|
|
201
|
+
}
|
|
202
|
+
for (const name of this.#lastComplete.keys()) {
|
|
203
|
+
if (!present.has(name)) this.#lastComplete.delete(name);
|
|
187
204
|
}
|
|
188
205
|
return rows;
|
|
189
206
|
}
|
|
@@ -248,6 +265,14 @@ export function isWaiting(row: AgentInfo): boolean {
|
|
|
248
265
|
return row.status === WAITING;
|
|
249
266
|
}
|
|
250
267
|
|
|
268
|
+
/** A syntactically complete state document, and the row it states when its
|
|
269
|
+
* process is still alive. A complete document for a dead process is distinct
|
|
270
|
+
* from a document caught between truncate and write: only the latter keeps the
|
|
271
|
+
* last complete row while the writer finishes. */
|
|
272
|
+
type RowResult =
|
|
273
|
+
| { readonly complete: false }
|
|
274
|
+
| { readonly complete: true; readonly row?: AgentInfo };
|
|
275
|
+
|
|
251
276
|
/** The conversion of one upstream document into the contract's spelling
|
|
252
277
|
* (§3.5): renamed to snake_case, instants in Unix ms, and nothing carried over
|
|
253
278
|
* that the contract does not name.
|
|
@@ -255,30 +280,33 @@ export function isWaiting(row: AgentInfo): boolean {
|
|
|
255
280
|
* A row whose process is gone is dropped: the file outlives a session that did
|
|
256
281
|
* not clean up after itself, and "the session exists" is what this input is
|
|
257
282
|
* for. */
|
|
258
|
-
function toRow(document: unknown, configDir: string, instance: InstanceId):
|
|
259
|
-
if (typeof document !== "object" || document === null) return
|
|
283
|
+
function toRow(document: unknown, configDir: string, instance: InstanceId): RowResult {
|
|
284
|
+
if (typeof document !== "object" || document === null) return { complete: false };
|
|
260
285
|
const raw = document as Record<string, unknown>;
|
|
261
286
|
const sid = text(raw["sessionId"]);
|
|
262
287
|
const pid = raw["pid"];
|
|
263
288
|
const cwd = text(raw["cwd"]);
|
|
264
289
|
const kind = text(raw["kind"]);
|
|
265
290
|
const startedAt = raw["startedAt"];
|
|
266
|
-
if (sid === undefined || cwd === undefined || kind === undefined) return
|
|
267
|
-
if (typeof pid !== "number" || typeof startedAt !== "number") return
|
|
268
|
-
if (!alive(pid)) return
|
|
291
|
+
if (sid === undefined || cwd === undefined || kind === undefined) return { complete: false };
|
|
292
|
+
if (typeof pid !== "number" || typeof startedAt !== "number") return { complete: false };
|
|
293
|
+
if (!alive(pid)) return { complete: true };
|
|
269
294
|
return {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
complete: true,
|
|
296
|
+
row: {
|
|
297
|
+
sid,
|
|
298
|
+
instance,
|
|
299
|
+
pid,
|
|
300
|
+
cwd,
|
|
301
|
+
kind,
|
|
302
|
+
started_at: startedAt,
|
|
303
|
+
config_dir: configDir,
|
|
304
|
+
...optional("name", text(raw["name"])),
|
|
305
|
+
...optional("status", text(raw["status"])),
|
|
306
|
+
...optional("waiting_for", text(raw["waitingFor"])),
|
|
307
|
+
...optional("state", text(raw["state"])),
|
|
308
|
+
...optional("background_id", text(raw["backgroundId"])),
|
|
309
|
+
},
|
|
282
310
|
};
|
|
283
311
|
}
|
|
284
312
|
|