doer-agent 0.7.9 → 0.8.1

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.
@@ -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 = Number(args.request.contextLines);
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
- if (!request.targetPath.startsWith("/")) {
220
- throw new Error("agent source requires an absolute directory path");
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 prefixResult = await runLocalCommand("git", ["-C", request.targetPath, "rev-parse", "--show-prefix"], request.targetPath);
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, request);
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, request, gitDiffResult.stdout);
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: {
@@ -187,6 +187,35 @@ export async function listAgentNotesLocal(workspaceRoot) {
187
187
  });
188
188
  return notes;
189
189
  }
190
+ export async function listAgentNotePatchesLocal(workspaceRoot, limit = 10) {
191
+ const rootAbs = workspacePath(workspaceRoot, PATCHES_ROOT);
192
+ const patches = [];
193
+ async function visit(absDir, relDir) {
194
+ const rows = await readdir(absDir, { withFileTypes: true }).catch(() => []);
195
+ await Promise.all(rows.map(async (row) => {
196
+ const childAbs = path.join(absDir, row.name);
197
+ const childRel = path.posix.join(relDir, row.name);
198
+ if (row.isDirectory()) {
199
+ await visit(childAbs, childRel);
200
+ return;
201
+ }
202
+ if (!row.isFile() || !row.name.endsWith(".patch")) {
203
+ return;
204
+ }
205
+ const entry = await stat(childAbs);
206
+ patches.push({
207
+ id: childRel,
208
+ path: childRel,
209
+ name: row.name,
210
+ size: entry.size,
211
+ mtimeMs: entry.mtimeMs,
212
+ });
213
+ }));
214
+ }
215
+ await visit(rootAbs, PATCHES_ROOT);
216
+ patches.sort((a, b) => b.mtimeMs - a.mtimeMs || b.path.localeCompare(a.path));
217
+ return patches.slice(0, Math.max(0, Math.trunc(limit)));
218
+ }
190
219
  export async function getAgentNoteLocal(workspaceRoot, noteId) {
191
220
  const id = sanitizeNoteId(noteId);
192
221
  const content = await readTextIfExists(workspacePath(workspaceRoot, noteRelPath(id)));
@@ -1,9 +1,10 @@
1
1
  import { StringCodec } from "nats";
2
- import { agentNotesCapabilitiesLocal, createAgentNoteLocal, deleteAgentNoteLocal, getAgentNoteLocal, listAgentNotesLocal, renameAgentNoteLocal, saveAgentNoteLocal, } from "./agent-notes-local.js";
2
+ import { agentNotesCapabilitiesLocal, createAgentNoteLocal, deleteAgentNoteLocal, listAgentNotePatchesLocal, getAgentNoteLocal, listAgentNotesLocal, renameAgentNoteLocal, saveAgentNoteLocal, } from "./agent-notes-local.js";
3
3
  const notesRpcCodec = StringCodec();
4
4
  function parseAction(value) {
5
5
  if (value === "capabilities" ||
6
6
  value === "list" ||
7
+ value === "listPatches" ||
7
8
  value === "get" ||
8
9
  value === "create" ||
9
10
  value === "save" ||
@@ -24,6 +25,10 @@ async function executeNotesRpc(workspaceRoot, request) {
24
25
  if (action === "list") {
25
26
  return { ok: true, action, notes: await listAgentNotesLocal(workspaceRoot) };
26
27
  }
28
+ if (action === "listPatches") {
29
+ const limit = typeof request.limit === "number" && Number.isFinite(request.limit) ? request.limit : 10;
30
+ return { ok: true, action, patches: await listAgentNotePatchesLocal(workspaceRoot, limit) };
31
+ }
27
32
  if (action === "get") {
28
33
  const notes = await listAgentNotesLocal(workspaceRoot);
29
34
  const noteId = stringValue(request.noteId) || notes[0]?.id || "";
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.7.9",
3
+ "version": "0.8.1",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",