agentosity 0.1.2 → 0.1.3
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/probes.js +48 -11
package/package.json
CHANGED
package/src/probes.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readdirSync, statSync, openSync, readSync, closeSync, fstatSync } from "node:fs";
|
|
1
|
+
import { readdirSync, statSync, openSync, readSync, closeSync, fstatSync, existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* 活跃度探针:回答"此刻 Agent 是否在干活"。
|
|
@@ -18,6 +19,7 @@ export function detectHarness(clientInfo) {
|
|
|
18
19
|
if (n.includes("gemini")) return "gemini-cli";
|
|
19
20
|
if (n.includes("cursor")) return "cursor";
|
|
20
21
|
if (n.includes("cline")) return "cline";
|
|
22
|
+
if (n.includes("opencode")) return "opencode";
|
|
21
23
|
return n || "unknown";
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -38,30 +40,65 @@ function codexSessionDir() {
|
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
/** Gemini CLI:~/.gemini/tmp/<sha256(cwd)>/ 下的日志文件 */
|
|
44
|
+
function geminiSessionDir() {
|
|
45
|
+
const hash = createHash("sha256").update(process.cwd()).digest("hex");
|
|
46
|
+
return join(homedir(), ".gemini", "tmp", hash);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 各 harness 的探针配置:
|
|
51
|
+
* - dir:目录内找"我们这个会话"的文件(新建或最近在写的)
|
|
52
|
+
* - file:固定文件,直接看 mtime(全局共享,精度低一档但胜过没有)
|
|
53
|
+
* - tail:文件是 jsonl 时才解析尾巴的在途工具调用
|
|
54
|
+
*/
|
|
55
|
+
const PROBE_TARGETS = {
|
|
56
|
+
"claude-code": { dir: claudeSessionDir, ext: ".jsonl", tail: true },
|
|
57
|
+
codex: { dir: codexSessionDir, ext: ".jsonl", tail: true },
|
|
58
|
+
"gemini-cli": { dir: geminiSessionDir, ext: "", tail: false },
|
|
59
|
+
opencode: {
|
|
60
|
+
file: () => join(homedir(), ".local", "share", "opencode", "opencode.db-wal"),
|
|
61
|
+
tail: false,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
41
65
|
export function createProbe(harness, processStartMs) {
|
|
66
|
+
const target = PROBE_TARGETS[harness];
|
|
42
67
|
const state = {
|
|
43
68
|
kind: "none",
|
|
44
69
|
file: null,
|
|
70
|
+
tail: target?.tail ?? false,
|
|
45
71
|
lastMtimeMs: 0,
|
|
46
72
|
lastWriteAt: 0,
|
|
47
73
|
};
|
|
48
74
|
|
|
49
|
-
const dirOf = { "claude-code": claudeSessionDir, codex: codexSessionDir }[harness];
|
|
50
|
-
|
|
51
75
|
function bindFile() {
|
|
52
|
-
if (state.file || !
|
|
76
|
+
if (state.file || !target) return;
|
|
53
77
|
try {
|
|
54
|
-
|
|
78
|
+
if (target.file) {
|
|
79
|
+
const p = target.file();
|
|
80
|
+
if (!existsSync(p)) return;
|
|
81
|
+
const st = statSync(p);
|
|
82
|
+
state.file = p;
|
|
83
|
+
state.kind = "file-mtime";
|
|
84
|
+
state.lastMtimeMs = st.mtimeMs;
|
|
85
|
+
state.lastWriteAt = Date.now();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const dir = target.dir();
|
|
55
89
|
const candidates = readdirSync(dir)
|
|
56
|
-
.filter((f) => f.endsWith(
|
|
90
|
+
.filter((f) => (target.ext ? f.endsWith(target.ext) : true))
|
|
57
91
|
.map((f) => {
|
|
58
92
|
const p = join(dir, f);
|
|
59
93
|
const st = statSync(p);
|
|
94
|
+
if (!st.isFile()) return null;
|
|
60
95
|
return { p, birth: st.birthtimeMs || st.ctimeMs, mtime: st.mtimeMs };
|
|
61
96
|
})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
// 会话文件可能是新建的(新会话),也可能是续用的老文件(resume):
|
|
99
|
+
// 新建:创建时间在进程启动前后;续用:进程启动后仍在被写入
|
|
100
|
+
.filter((c) => c.birth >= processStartMs - 120_000 || c.mtime >= processStartMs - 5_000)
|
|
101
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
65
102
|
if (candidates.length > 0) {
|
|
66
103
|
state.file = candidates[0].p;
|
|
67
104
|
state.kind = "file-mtime";
|
|
@@ -90,9 +127,9 @@ export function createProbe(harness, processStartMs) {
|
|
|
90
127
|
}
|
|
91
128
|
}
|
|
92
129
|
|
|
93
|
-
/** 信号 2:尾巴状态 — 最后一个事件是发起工具调用且尚无结果 → 工具在途 */
|
|
130
|
+
/** 信号 2:尾巴状态 — 最后一个事件是发起工具调用且尚无结果 → 工具在途(仅 jsonl) */
|
|
94
131
|
function toolInFlight() {
|
|
95
|
-
if (!state.file) return false;
|
|
132
|
+
if (!state.file || !state.tail) return false;
|
|
96
133
|
try {
|
|
97
134
|
const fd = openSync(state.file, "r");
|
|
98
135
|
const size = fstatSync(fd).size;
|