@agentconnect.md/daemon 1.0.0-rc.66 → 1.0.0-rc.68

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/index.js CHANGED
@@ -13322,6 +13322,50 @@ const WorkspaceReadContent = object({
13322
13322
  nextOffset: number().int().nonnegative().optional(),
13323
13323
  truncated: boolean().optional()
13324
13324
  });
13325
+ /**
13326
+ * Agent workspace git ops (C→D REQ → REP) — the console's on-demand controls for
13327
+ * a git-repo workspace. Like list/read these run daemon-local; the CP proxies the
13328
+ * outcome and stores nothing.
13329
+ *
13330
+ * These are DATA-oriented: a non-repo workspace (`isRepo:false`), a dirty tree,
13331
+ * or a pull that can't fast-forward (offline, diverged, local edits) all come
13332
+ * back as a normal REP — NOT an `error` frame. Only an unknown agent
13333
+ * (`BAD_PAYLOAD`) or an unexpected git/fs failure (`INTERNAL`) is an error.
13334
+ */
13335
+ /** C→D REQ: report `git status` of the agent's workspace (is it clean?). */
13336
+ const WorkspaceGitStatusReq = object({ agentId: string().min(1) });
13337
+ /** One changed path in a `git status`. `index`/`workingDir` are git's per-file XY
13338
+ * status chars (' ', 'M', 'A', 'D', 'R', 'C', 'U', '?', …). */
13339
+ const WorkspaceGitFile = object({
13340
+ path: string(),
13341
+ index: string(),
13342
+ workingDir: string()
13343
+ });
13344
+ /** D→C REP (corr = the req id): the working-tree status. */
13345
+ const WorkspaceGitStatus = object({
13346
+ agentId: string(),
13347
+ isRepo: boolean(),
13348
+ clean: boolean(),
13349
+ branch: string().optional(),
13350
+ tracking: string().optional(),
13351
+ ahead: number().int().nonnegative().optional(),
13352
+ behind: number().int().nonnegative().optional(),
13353
+ files: array(WorkspaceGitFile).optional(),
13354
+ truncated: boolean().optional()
13355
+ });
13356
+ /** C→D REQ: force a fast-forward-only `git pull` in the agent's workspace now. */
13357
+ const WorkspaceGitPullReq = object({ agentId: string().min(1) });
13358
+ /** D→C REP (corr = the req id): the pull outcome. A failed pull is DATA
13359
+ * (`ok:false` + `detail`), not an error frame (see the section docblock). */
13360
+ const WorkspaceGitPullResult = object({
13361
+ agentId: string(),
13362
+ isRepo: boolean(),
13363
+ ok: boolean(),
13364
+ detail: string().optional(),
13365
+ changed: number().int().nonnegative().optional(),
13366
+ insertions: number().int().nonnegative().optional(),
13367
+ deletions: number().int().nonnegative().optional()
13368
+ });
13325
13369
  //#endregion
13326
13370
  //#region ../protocol/dist/frames/telemetry.js
13327
13371
  /**
@@ -13446,6 +13490,10 @@ const FRAME_SCHEMAS = {
13446
13490
  "workspace/list/page": WorkspaceListPage,
13447
13491
  "workspace/read": WorkspaceReadReq,
13448
13492
  "workspace/read/content": WorkspaceReadContent,
13493
+ "workspace/gitstatus": WorkspaceGitStatusReq,
13494
+ "workspace/gitstatus/result": WorkspaceGitStatus,
13495
+ "workspace/gitpull": WorkspaceGitPullReq,
13496
+ "workspace/gitpull/result": WorkspaceGitPullResult,
13449
13497
  "config/push": object({ keys: record(string(), unknown()) }),
13450
13498
  "daemon/restart": object({
13451
13499
  reason: string(),
@@ -13545,6 +13593,10 @@ discriminatedUnion("type", [
13545
13593
  frame("workspace/list/page", FRAME_SCHEMAS["workspace/list/page"]),
13546
13594
  frame("workspace/read", FRAME_SCHEMAS["workspace/read"]),
13547
13595
  frame("workspace/read/content", FRAME_SCHEMAS["workspace/read/content"]),
13596
+ frame("workspace/gitstatus", FRAME_SCHEMAS["workspace/gitstatus"]),
13597
+ frame("workspace/gitstatus/result", FRAME_SCHEMAS["workspace/gitstatus/result"]),
13598
+ frame("workspace/gitpull", FRAME_SCHEMAS["workspace/gitpull"]),
13599
+ frame("workspace/gitpull/result", FRAME_SCHEMAS["workspace/gitpull/result"]),
13548
13600
  frame("config/push", FRAME_SCHEMAS["config/push"]),
13549
13601
  frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
13550
13602
  frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
@@ -13671,7 +13723,7 @@ function isFrame(type) {
13671
13723
  }
13672
13724
  //#endregion
13673
13725
  //#region src/workspace/workspace-manager.ts
13674
- const PULL_TIMEOUT_MS = 4500;
13726
+ const PULL_TIMEOUT_MS$1 = 4500;
13675
13727
  const cloneInFlight = /* @__PURE__ */ new Map();
13676
13728
  async function prepareWorkspace(agent) {
13677
13729
  const cwd = agent.workspace.path;
@@ -13686,7 +13738,7 @@ async function prepareWorkspace(agent) {
13686
13738
  return cwd;
13687
13739
  }
13688
13740
  if (agent.workspace.pullOnNewSession) try {
13689
- await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS))]);
13741
+ await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS$1))]);
13690
13742
  } catch {}
13691
13743
  return cwd;
13692
13744
  }
@@ -82763,6 +82815,12 @@ var CpClient = class {
82763
82815
  case "workspace/read":
82764
82816
  this.deps.workspaceRead.read(frame.payload).then((content) => this.reply(frame, "workspace/read/content", content)).catch((err) => this.workspaceError(frame.id, "workspace/read", err));
82765
82817
  return;
82818
+ case "workspace/gitstatus":
82819
+ this.deps.workspaceGit.status(frame.payload.agentId).then((status) => this.reply(frame, "workspace/gitstatus/result", status)).catch((err) => this.workspaceError(frame.id, "workspace/gitstatus", err));
82820
+ return;
82821
+ case "workspace/gitpull":
82822
+ this.deps.workspaceGit.pull(frame.payload.agentId).then((result) => this.reply(frame, "workspace/gitpull/result", result)).catch((err) => this.workspaceError(frame.id, "workspace/gitpull", err));
82823
+ return;
82766
82824
  case "agent/prompt":
82767
82825
  this.sendError(frame.id, "INTERNAL", "agent/prompt not implemented", false);
82768
82826
  return;
@@ -82860,6 +82918,104 @@ function createSessionReader(store) {
82860
82918
  };
82861
82919
  }
82862
82920
  //#endregion
82921
+ //#region src/cp/workspace-git.ts
82922
+ /**
82923
+ * `WorkspaceGit` — the seam answering the CP's `workspace/gitstatus` and
82924
+ * `workspace/gitpull` REQs against an agent's local git-repo workspace. Sibling
82925
+ * to {@link WorkspaceReader}: git runs daemon-local and only the outcome is
82926
+ * proxied to the console (§1/§12), never the repo.
82927
+ *
82928
+ * DATA vs error: a from-scratch workspace (`isRepo:false`), a dirty tree, or a
82929
+ * pull that can't fast-forward (offline, diverged, would clobber local edits) are
82930
+ * all normal REPs — the caller renders them. Only an unknown agentId throws
82931
+ * ({@link WorkspaceViolationError} → `BAD_PAYLOAD`); an unexpected git/fs failure
82932
+ * on `status` propagates (→ `INTERNAL`). Git-provided text is scrubbed of the
82933
+ * absolute workspace path before it leaves the daemon (raw git errors embed host
82934
+ * paths that must not reach the CP/UI).
82935
+ */
82936
+ /** On-demand pull is interactive, so allow more headroom than the 4.5s
82937
+ * best-effort pull at session start (workspace-manager.ts). */
82938
+ const PULL_TIMEOUT_MS = 2e4;
82939
+ /** Cap the changed-file list so a huge working tree can't overflow the frame. */
82940
+ const MAX_STATUS_FILES = 500;
82941
+ function createWorkspaceGit(workspaceRootByAgent) {
82942
+ function rootFor(agentId) {
82943
+ const root = workspaceRootByAgent(agentId);
82944
+ if (!root) throw new WorkspaceViolationError(`unknown agent "${agentId}"`);
82945
+ return root;
82946
+ }
82947
+ /** A git-repo workspace has a `.git` at its root; from-scratch does not. */
82948
+ function isRepo(root) {
82949
+ return existsSync(join(root, ".git"));
82950
+ }
82951
+ /** Strip the absolute workspace path out of git-provided text so no host path
82952
+ * leaks to the CP/UI. Repo-relative paths in the message are kept. */
82953
+ function scrub(root, msg) {
82954
+ return msg.split(root).join("<workspace>").trim();
82955
+ }
82956
+ return {
82957
+ async status(agentId) {
82958
+ const root = rootFor(agentId);
82959
+ if (!isRepo(root)) return {
82960
+ agentId,
82961
+ isRepo: false,
82962
+ clean: true
82963
+ };
82964
+ const s = await simpleGit(root).status();
82965
+ const files = s.files.slice(0, MAX_STATUS_FILES).map((f) => ({
82966
+ path: f.path,
82967
+ index: f.index,
82968
+ workingDir: f.working_dir
82969
+ }));
82970
+ return {
82971
+ agentId,
82972
+ isRepo: true,
82973
+ clean: s.isClean(),
82974
+ ...s.current ? { branch: s.current } : {},
82975
+ ...s.tracking ? { tracking: s.tracking } : {},
82976
+ ahead: s.ahead,
82977
+ behind: s.behind,
82978
+ ...files.length ? { files } : {},
82979
+ ...s.files.length > files.length ? { truncated: true } : {}
82980
+ };
82981
+ },
82982
+ async pull(agentId) {
82983
+ const root = rootFor(agentId);
82984
+ if (!isRepo(root)) return {
82985
+ agentId,
82986
+ isRepo: false,
82987
+ ok: false,
82988
+ detail: "workspace is not a git checkout"
82989
+ };
82990
+ let timer;
82991
+ try {
82992
+ const res = await Promise.race([simpleGit(root).pull(["--ff-only"]), new Promise((_, rej) => {
82993
+ timer = setTimeout(() => rej(/* @__PURE__ */ new Error("pull timed out")), PULL_TIMEOUT_MS);
82994
+ })]);
82995
+ const changed = res.files.length;
82996
+ return {
82997
+ agentId,
82998
+ isRepo: true,
82999
+ ok: true,
83000
+ changed,
83001
+ insertions: res.summary.insertions,
83002
+ deletions: res.summary.deletions,
83003
+ detail: changed > 0 ? `Fast-forwarded — updated ${changed} file${changed === 1 ? "" : "s"}.` : "Already up to date."
83004
+ };
83005
+ } catch (err) {
83006
+ return {
83007
+ agentId,
83008
+ isRepo: true,
83009
+ ok: false,
83010
+ detail: scrub(root, err?.message ?? "pull failed")
83011
+ };
83012
+ } finally {
83013
+ if (timer) clearTimeout(timer);
83014
+ }
83015
+ }
83016
+ };
83017
+ }
83018
+ //#endregion
82863
83019
  //#region src/cp/cp-cron.ts
82864
83020
  /**
82865
83021
  * `CpCronRegistry` — the daemon's set of CP-pushed crons, keyed by `cronId`
@@ -84368,6 +84524,7 @@ var Daemon = class Daemon {
84368
84524
  configApply: this.cpConfigApply(),
84369
84525
  sessionRead: createSessionReader(this.store),
84370
84526
  workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
84527
+ workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
84371
84528
  clock: systemClock,
84372
84529
  connect: () => ClientTransport.dial(url),
84373
84530
  log: this.log