doer-agent 0.7.8 → 0.8.0
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/dist/agent-git-rpc.js +47 -10
- package/dist/agent-notes-local.js +69 -4
- package/dist/agent-runtime-utils.js +5 -1
- package/dist/agent.js +2 -0
- package/package.json +1 -1
package/dist/agent-git-rpc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { StringCodec } from "nats";
|
|
3
4
|
const gitRpcCodec = StringCodec();
|
|
4
5
|
function runLocalCommand(command, args, cwd) {
|
|
@@ -43,6 +44,35 @@ function sanitizeGitPathspec(value) {
|
|
|
43
44
|
}
|
|
44
45
|
return trimmed;
|
|
45
46
|
}
|
|
47
|
+
function normalizeWorkspacePath(workspaceRoot, value) {
|
|
48
|
+
const trimmed = value.trim().replace(/\\/g, "/") || ".";
|
|
49
|
+
if (path.isAbsolute(trimmed)) {
|
|
50
|
+
throw new Error("absolute paths are not supported for agent git diff");
|
|
51
|
+
}
|
|
52
|
+
const abs = path.resolve(workspaceRoot, trimmed);
|
|
53
|
+
if (abs !== workspaceRoot && !abs.startsWith(workspaceRoot + path.sep)) {
|
|
54
|
+
throw new Error("path escapes workspace root");
|
|
55
|
+
}
|
|
56
|
+
return abs;
|
|
57
|
+
}
|
|
58
|
+
function pathspecsForRepo(args) {
|
|
59
|
+
const normalizedTargetPath = args.targetPath.trim().replace(/\\/g, "/").replace(/\/+$/g, "") || ".";
|
|
60
|
+
return args.pathspecs.map((pathspec) => {
|
|
61
|
+
if (path.isAbsolute(pathspec)) {
|
|
62
|
+
throw new Error("absolute pathspecs are not supported for agent git diff");
|
|
63
|
+
}
|
|
64
|
+
const normalizedPathspec = pathspec.trim().replace(/\\/g, "/").replace(/\/+$/g, "") || ".";
|
|
65
|
+
const workspaceAbs = normalizedPathspec === normalizedTargetPath
|
|
66
|
+
? args.targetPathAbs
|
|
67
|
+
: path.resolve(args.workspaceRoot, pathspec);
|
|
68
|
+
const repoRelRaw = path.relative(args.repoRootAbs, workspaceAbs).split(path.sep).join("/");
|
|
69
|
+
const repoRel = repoRelRaw || ".";
|
|
70
|
+
if (repoRel && repoRel !== ".." && !repoRel.startsWith("../")) {
|
|
71
|
+
return repoRel;
|
|
72
|
+
}
|
|
73
|
+
return pathspec;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
46
76
|
function normalizeGitRpcRequest(args) {
|
|
47
77
|
const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
|
|
48
78
|
const responseSubject = typeof args.request.responseSubject === "string" ? args.request.responseSubject.trim() : "";
|
|
@@ -68,7 +98,7 @@ function normalizeGitRpcRequest(args) {
|
|
|
68
98
|
args.request.diffAlgorithm === "histogram"
|
|
69
99
|
? args.request.diffAlgorithm
|
|
70
100
|
: "default";
|
|
71
|
-
const contextRaw =
|
|
101
|
+
const contextRaw = typeof args.request.contextLines === "number" ? args.request.contextLines : Number.NaN;
|
|
72
102
|
const contextLines = Number.isFinite(contextRaw) ? Math.max(0, Math.min(200, Math.trunc(contextRaw))) : null;
|
|
73
103
|
const pathspecs = Array.isArray(args.request.pathspecs) ? args.request.pathspecs.map((item) => sanitizeGitPathspec(item)) : [];
|
|
74
104
|
return {
|
|
@@ -216,10 +246,9 @@ export async function handleGitRpcMessage(args) {
|
|
|
216
246
|
const request = normalizeGitRpcRequest({ request: payload, agentId: args.agentId });
|
|
217
247
|
requestId = request.requestId;
|
|
218
248
|
responseSubject = request.responseSubject;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const topLevelResult = await runLocalCommand("git", ["-C", request.targetPath, "rev-parse", "--show-toplevel"], request.targetPath);
|
|
249
|
+
const workspaceRoot = path.resolve(args.workspaceRoot);
|
|
250
|
+
const targetPathAbs = normalizeWorkspacePath(workspaceRoot, request.targetPath);
|
|
251
|
+
const topLevelResult = await runLocalCommand("git", ["-C", targetPathAbs, "rev-parse", "--show-toplevel"], targetPathAbs);
|
|
223
252
|
if (topLevelResult.code !== 0) {
|
|
224
253
|
publishGitRpcResponse({
|
|
225
254
|
nc: args.nc,
|
|
@@ -233,7 +262,6 @@ export async function handleGitRpcMessage(args) {
|
|
|
233
262
|
source: "agent",
|
|
234
263
|
agent: { id: args.agentId, name: null },
|
|
235
264
|
currentPath: request.targetPath,
|
|
236
|
-
repoRoot: null,
|
|
237
265
|
repoRelativePath: null,
|
|
238
266
|
branch: null,
|
|
239
267
|
gitDiff: {
|
|
@@ -249,17 +277,27 @@ export async function handleGitRpcMessage(args) {
|
|
|
249
277
|
return;
|
|
250
278
|
}
|
|
251
279
|
const repoRootAbs = topLevelResult.stdout.trim();
|
|
252
|
-
const
|
|
280
|
+
const repoRequest = {
|
|
281
|
+
...request,
|
|
282
|
+
pathspecs: pathspecsForRepo({
|
|
283
|
+
workspaceRoot,
|
|
284
|
+
repoRootAbs,
|
|
285
|
+
targetPath: request.targetPath,
|
|
286
|
+
targetPathAbs,
|
|
287
|
+
pathspecs: request.pathspecs,
|
|
288
|
+
}),
|
|
289
|
+
};
|
|
290
|
+
const prefixResult = await runLocalCommand("git", ["-C", targetPathAbs, "rev-parse", "--show-prefix"], targetPathAbs);
|
|
253
291
|
const repoRelativePath = prefixResult.code === 0 ? (prefixResult.stdout.trim().replace(/\/$/, "") || ".") : ".";
|
|
254
292
|
const branchResult = await runLocalCommand("git", ["-C", repoRootAbs, "symbolic-ref", "--quiet", "--short", "HEAD"], repoRootAbs);
|
|
255
293
|
const detachedResult = branchResult.code === 0 ? null : await runLocalCommand("git", ["-C", repoRootAbs, "rev-parse", "--short", "HEAD"], repoRootAbs);
|
|
256
294
|
const branch = branchResult.code === 0 ? branchResult.stdout.trim() || null : detachedResult && detachedResult.code === 0 ? detachedResult.stdout.trim() || null : null;
|
|
257
|
-
const gitDiffArgs = buildAgentGitDiffArgs(repoRootAbs,
|
|
295
|
+
const gitDiffArgs = buildAgentGitDiffArgs(repoRootAbs, repoRequest);
|
|
258
296
|
const gitDiffResult = await runLocalCommand("git", gitDiffArgs.args, repoRootAbs);
|
|
259
297
|
if (gitDiffResult.code !== 0) {
|
|
260
298
|
throw new Error(gitDiffResult.stderr.trim() || "Failed to run agent git diff");
|
|
261
299
|
}
|
|
262
|
-
const withUntracked = await appendAgentLocalUntrackedDiff(repoRootAbs,
|
|
300
|
+
const withUntracked = await appendAgentLocalUntrackedDiff(repoRootAbs, repoRequest, gitDiffResult.stdout);
|
|
263
301
|
publishGitRpcResponse({
|
|
264
302
|
nc: args.nc,
|
|
265
303
|
responseSubject,
|
|
@@ -272,7 +310,6 @@ export async function handleGitRpcMessage(args) {
|
|
|
272
310
|
source: "agent",
|
|
273
311
|
agent: { id: args.agentId, name: null },
|
|
274
312
|
currentPath: request.targetPath,
|
|
275
|
-
repoRoot: repoRootAbs,
|
|
276
313
|
repoRelativePath,
|
|
277
314
|
branch,
|
|
278
315
|
gitDiff: {
|
|
@@ -4,18 +4,83 @@ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/p
|
|
|
4
4
|
import { formatPatch, structuredPatch } from "diff";
|
|
5
5
|
const AGENT_NOTES_ROOT = ".doer-agent/notes";
|
|
6
6
|
const PATCHES_ROOT = `${AGENT_NOTES_ROOT}/patches`;
|
|
7
|
+
function resolveSystemTimeZone() {
|
|
8
|
+
const configured = process.env.TZ?.trim();
|
|
9
|
+
if (configured) {
|
|
10
|
+
return configured;
|
|
11
|
+
}
|
|
12
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
13
|
+
}
|
|
14
|
+
function resolveTimeZoneOffsetString(date, timeZone) {
|
|
15
|
+
try {
|
|
16
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
17
|
+
timeZone,
|
|
18
|
+
timeZoneName: "shortOffset",
|
|
19
|
+
hour: "2-digit",
|
|
20
|
+
minute: "2-digit",
|
|
21
|
+
hour12: false,
|
|
22
|
+
}).formatToParts(date);
|
|
23
|
+
const token = parts.find((part) => part.type === "timeZoneName")?.value || "GMT+0";
|
|
24
|
+
const matched = token.match(/GMT([+-]\d{1,2})(?::?(\d{2}))?/i);
|
|
25
|
+
if (!matched) {
|
|
26
|
+
return "+00:00";
|
|
27
|
+
}
|
|
28
|
+
const hourRaw = matched[1] || "+0";
|
|
29
|
+
const minuteRaw = matched[2] || "00";
|
|
30
|
+
const sign = hourRaw.startsWith("-") ? "-" : "+";
|
|
31
|
+
const absHour = String(Math.abs(Number.parseInt(hourRaw, 10))).padStart(2, "0");
|
|
32
|
+
const absMinute = String(Math.abs(Number.parseInt(minuteRaw, 10))).padStart(2, "0");
|
|
33
|
+
return `${sign}${absHour}:${absMinute}`;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return "+00:00";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function systemTimeParts(date) {
|
|
40
|
+
const timeZone = resolveSystemTimeZone();
|
|
41
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
42
|
+
timeZone,
|
|
43
|
+
year: "numeric",
|
|
44
|
+
month: "2-digit",
|
|
45
|
+
day: "2-digit",
|
|
46
|
+
hour: "2-digit",
|
|
47
|
+
minute: "2-digit",
|
|
48
|
+
second: "2-digit",
|
|
49
|
+
hour12: false,
|
|
50
|
+
hourCycle: "h23",
|
|
51
|
+
}).formatToParts(date);
|
|
52
|
+
const pick = (type) => {
|
|
53
|
+
return parts.find((part) => part.type === type)?.value || "00";
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
year: pick("year"),
|
|
57
|
+
month: pick("month"),
|
|
58
|
+
day: pick("day"),
|
|
59
|
+
hours: pick("hour"),
|
|
60
|
+
minutes: pick("minute"),
|
|
61
|
+
seconds: pick("second"),
|
|
62
|
+
milliseconds: String(date.getMilliseconds()).padStart(3, "0"),
|
|
63
|
+
offset: resolveTimeZoneOffsetString(date, timeZone),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
7
66
|
function nowIso() {
|
|
8
|
-
|
|
67
|
+
const parts = systemTimeParts(new Date());
|
|
68
|
+
return `${parts.year}-${parts.month}-${parts.day}T${parts.hours}:${parts.minutes}:${parts.seconds}.${parts.milliseconds}${parts.offset}`;
|
|
9
69
|
}
|
|
10
70
|
function createTimeBasedPatchId(createdAt) {
|
|
11
|
-
const stamp = createdAt
|
|
71
|
+
const stamp = createdAt
|
|
72
|
+
.replace("T", "t")
|
|
73
|
+
.replace(/([+-])(\d{2}):?(\d{2})$/, (_match, sign, hours, minutes) => {
|
|
74
|
+
return `${sign === "-" ? "m" : "p"}${hours}${minutes}`;
|
|
75
|
+
})
|
|
76
|
+
.replace(/[-:.]/g, "")
|
|
77
|
+
.replace("Z", "z");
|
|
12
78
|
const random = Math.random().toString(36).slice(2, 8);
|
|
13
79
|
return `${stamp}-${random}`;
|
|
14
80
|
}
|
|
15
81
|
function patchRelPath(id, createdAt) {
|
|
16
82
|
const date = new Date(createdAt);
|
|
17
|
-
const year =
|
|
18
|
-
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
83
|
+
const { year, month } = systemTimeParts(Number.isNaN(date.getTime()) ? new Date() : date);
|
|
19
84
|
return path.posix.join(PATCHES_ROOT, year, month, `${id}.patch`);
|
|
20
85
|
}
|
|
21
86
|
export function sanitizeNoteId(value) {
|
|
@@ -171,7 +171,10 @@ export function writeRpcStream(requestId, stream, chunk) {
|
|
|
171
171
|
}
|
|
172
172
|
function resolveLogTimeZone() {
|
|
173
173
|
const configured = process.env.DOER_AGENT_LOG_TIMEZONE?.trim() || process.env.TZ?.trim();
|
|
174
|
-
|
|
174
|
+
if (configured && configured.length > 0) {
|
|
175
|
+
return configured;
|
|
176
|
+
}
|
|
177
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
175
178
|
}
|
|
176
179
|
function resolveTimeZoneOffsetString(date, timeZone) {
|
|
177
180
|
try {
|
|
@@ -210,6 +213,7 @@ export function formatLocalTimestamp(date = new Date()) {
|
|
|
210
213
|
minute: "2-digit",
|
|
211
214
|
second: "2-digit",
|
|
212
215
|
hour12: false,
|
|
216
|
+
hourCycle: "h23",
|
|
213
217
|
}).formatToParts(date);
|
|
214
218
|
const pick = (type) => {
|
|
215
219
|
return parts.find((part) => part.type === type)?.value || "00";
|
package/dist/agent.js
CHANGED
|
@@ -84,6 +84,7 @@ function subscribeToGitRpc(args) {
|
|
|
84
84
|
msg,
|
|
85
85
|
nc: args.jetstream.nc,
|
|
86
86
|
agentId: args.agentId,
|
|
87
|
+
workspaceRoot: args.workspaceRoot,
|
|
87
88
|
onError: writeAgentError,
|
|
88
89
|
});
|
|
89
90
|
},
|
|
@@ -319,6 +320,7 @@ async function main() {
|
|
|
319
320
|
jetstream,
|
|
320
321
|
userId,
|
|
321
322
|
agentId: initialAgentId,
|
|
323
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
322
324
|
});
|
|
323
325
|
subscribeToSkillRpc({
|
|
324
326
|
nc: jetstream.nc,
|