@blogic-cz/agent-tools 0.15.8 → 0.15.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.15.8",
3
+ "version": "0.15.11",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -1,11 +1,12 @@
1
1
  import { Effect } from "effect";
2
2
  import { readdir } from "node:fs/promises";
3
+ import { basename, join } from "node:path";
3
4
 
4
5
  import type { MessageSummary } from "./types";
5
6
 
6
7
  import { SessionReadError, SessionStorageNotFoundError, type SessionError } from "./errors";
7
8
 
8
- export const encodeProjectPath = (dir: string): string => dir.replaceAll("/", "-");
9
+ export const encodeProjectPath = (dir: string): string => dir.replaceAll(/[/\\:]/g, "-");
9
10
 
10
11
  export type ContentBlock =
11
12
  | { type: "text"; text: string }
@@ -172,7 +173,7 @@ export const getClaudeCodeSessions = (
172
173
  continue;
173
174
  }
174
175
 
175
- const projectPath = `${basePath}/${entry.name}`;
176
+ const projectPath = join(basePath, entry.name);
176
177
  let files: string[];
177
178
  try {
178
179
  // eslint-disable-next-line eslint/no-await-in-loop -- directory scan must finish before filtering file list
@@ -186,7 +187,7 @@ export const getClaudeCodeSessions = (
186
187
  continue;
187
188
  }
188
189
 
189
- sessionFiles.push(`${projectPath}/${fileName}`);
190
+ sessionFiles.push(join(projectPath, fileName));
190
191
  }
191
192
  }
192
193
 
@@ -200,8 +201,8 @@ export const getClaudeCodeSessions = (
200
201
  });
201
202
 
202
203
  const getFileNameWithoutExtension = (filePath: string): string => {
203
- const fileName = filePath.split("/").pop();
204
- if (fileName === undefined) {
204
+ const fileName = basename(filePath);
205
+ if (fileName === "") {
205
206
  return "";
206
207
  }
207
208
 
@@ -2,6 +2,7 @@ import { Effect } from "effect";
2
2
  // node:fs/promises, not Bun.file/Bun.Glob: vitest runs under Node, where the Bun globals do not
3
3
  // exist, and every function below is reached by the suite. See AGENTS.md.
4
4
  import { open, readFile, readdir, stat } from "node:fs/promises";
5
+ import { join } from "node:path";
5
6
 
6
7
  import type { MessageSummary, SessionSummary } from "./types";
7
8
 
@@ -117,10 +118,10 @@ const walkSessionFiles = async (basePath: string): Promise<string[]> => {
117
118
  directories
118
119
  .filter((entry) => entry.isDirectory())
119
120
  .map(async (directory) => {
120
- const directoryPath = `${basePath}/${directory.name}`;
121
+ const directoryPath = join(basePath, directory.name);
121
122
  return (await readdir(directoryPath, { withFileTypes: true }))
122
123
  .filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
123
- .map((entry) => `${directoryPath}/${entry.name}`);
124
+ .map((entry) => join(directoryPath, entry.name));
124
125
  }),
125
126
  );
126
127
  return files.flat();
@@ -1,5 +1,6 @@
1
1
  import { Context, Effect, Layer } from "effect";
2
2
  import { readdir } from "node:fs/promises";
3
+ import { basename } from "node:path";
3
4
 
4
5
  import type { MessageSummary, SessionInfo, SessionSource, SessionSummary } from "./types";
5
6
 
@@ -52,7 +53,7 @@ const UUID_SESSION_ID_REGEX =
52
53
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/u;
53
54
 
54
55
  const getSessionIdFromClaudeFile = (filePath: string): string => {
55
- const fileName = filePath.split("/").pop() ?? "";
56
+ const fileName = basename(filePath);
56
57
  return fileName.endsWith(".jsonl") ? fileName.slice(0, -".jsonl".length) : fileName;
57
58
  };
58
59
 
@@ -333,7 +334,7 @@ export class SessionService extends Context.Service<
333
334
 
334
335
  summaries.push({
335
336
  sessionID: parsed.sessionID ?? sessionId,
336
- id: parsed.id ?? filePath.split("/").pop()?.replace(".json", "") ?? "",
337
+ id: parsed.id ?? basename(filePath).replace(".json", ""),
337
338
  title: parsed.summary.title,
338
339
  body: parsed.summary.body ?? "",
339
340
  created: parsed.time?.created ?? 0,
@@ -557,10 +557,23 @@ const acquireVpn = <E>(
557
557
  while (true) {
558
558
  const snapshot = store.snapshot();
559
559
  if (snapshot.lifecycle === "UNKNOWN") {
560
+ const contended = snapshot.activeLeases > 0;
561
+ if (!contended) {
562
+ const observed = yield* runStatusBefore(driver, connectDeadline, runCommand);
563
+ if (
564
+ observed !== undefined &&
565
+ (store.reconcileUnknown(snapshot, observed, Date.now()) ||
566
+ store.snapshot().lifecycle !== "UNKNOWN")
567
+ ) {
568
+ continue;
569
+ }
570
+ }
560
571
  return yield* fail(
561
572
  new PrerequisiteRunError({
562
573
  message: `VPN prerequisite "${key}" has unknown ownership: ${snapshot.evidence ?? "no evidence"}`,
563
- hint: "Quiesce all agent-tools processes and remove its VPN runtime state before retrying.",
574
+ hint: contended
575
+ ? "Another agent-tools process holds an active lease on this VPN. Retry once it has finished."
576
+ : missingVpnToolHint(driver),
564
577
  }),
565
578
  );
566
579
  }
@@ -534,6 +534,28 @@ export class VpnStore {
534
534
  return false;
535
535
  }
536
536
 
537
+ /** Recovers terminal UNKNOWN from an observed status; fenced so it can never take ownership from a holder. */
538
+ reconcileUnknown(snapshot: VpnStateSnapshot, connected: boolean, now: number): boolean {
539
+ return this.immediate(
540
+ () =>
541
+ this.db
542
+ .query(
543
+ `UPDATE vpn_state SET lifecycle=?, managed_epoch_id=NULL, idle_deadline=NULL,
544
+ revision=revision+1, evidence=?, updated_at=?
545
+ WHERE singleton=1 AND lifecycle='UNKNOWN' AND operation_id IS NULL AND revision=?
546
+ AND NOT EXISTS (SELECT 1 FROM leases WHERE status='ACTIVE')`,
547
+ )
548
+ .run(
549
+ connected ? "EXTERNAL" : "DOWN",
550
+ connected
551
+ ? "Recovered from unknown ownership: VPN observed connected outside agent-tools."
552
+ : "Recovered from unknown ownership: VPN observed disconnected.",
553
+ now,
554
+ snapshot.revision,
555
+ ).changes === 1,
556
+ );
557
+ }
558
+
537
559
  private commitOperation(
538
560
  guard: OperationGuard,
539
561
  from: VpnLifecycle,