@myagentroam/node 0.11.44 → 0.11.45

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,4 @@
1
- import { compactRunnerText, compactRunnerValue, publicOutputSnapshot } from '../normalization.js';
1
+ import { compactRunnerText, compactRunnerValue, publicToolOutput } from '../normalization.js';
2
2
  import { shellExecution } from '../execution-presentation.js';
3
3
  export function normalizeOpenCodeTool(toolName, state) {
4
4
  const status = state?.status === 'completed'
@@ -21,14 +21,14 @@ export function normalizeOpenCodeTool(toolName, state) {
21
21
  ? input.cmd
22
22
  : publicToolName, 10_000),
23
23
  cwd: typeof input?.cwd === 'string' ? compactRunnerText(input.cwd, 4_096) : null,
24
- outputPreview: typeof state?.output === 'string' ? compactRunnerText(state.output, 10_000) : '',
25
24
  exitCode: typeof state?.exitCode === 'number' ? state.exitCode : null,
26
25
  durationMs: null,
27
- truncated: typeof state?.output === 'string' && state.output.length > 10_000,
28
26
  ...(typeof state?.output === 'string'
29
- ? { output: publicOutputSnapshot(state.output) }
27
+ ? { output: publicToolOutput(state.output, undefined, 'COMBINED') }
30
28
  : {}),
31
- toolPresentation: shellExecution(typeof state?.output === 'string' ? state.output : undefined)
29
+ toolPresentation: shellExecution((typeof input?.command === 'string' && input.command.length > 10_000) ||
30
+ (typeof input?.cmd === 'string' && input.cmd.length > 10_000) ||
31
+ (typeof input?.cwd === 'string' && input.cwd.length > 4_096))
32
32
  }
33
33
  };
34
34
  return {
@@ -39,10 +39,10 @@ export function normalizeOpenCodeTool(toolName, state) {
39
39
  title: compactRunnerText(typeof state?.title === 'string' ? state.title : toolName, 500) ||
40
40
  publicToolName,
41
41
  inputSummary: compactRunnerValue(state?.input, 10_000).text || null,
42
- outputSummary: typeof state?.output === 'string' ? compactRunnerText(state.output, 10_000) : null,
42
+ outputSummary: null,
43
43
  progressLabel: null,
44
44
  errorCode: status === 'FAILED' ? 'OPENCODE_TOOL_FAILED' : null,
45
- truncated: false,
45
+ ...(compactRunnerValue(state?.input, 10_000).truncated ? { inputTruncated: true } : {}),
46
46
  ...(normalizedName === 'read'
47
47
  ? {
48
48
  toolPresentation: {
@@ -53,7 +53,7 @@ export function normalizeOpenCodeTool(toolName, state) {
53
53
  }
54
54
  }
55
55
  : {}),
56
- ...(typeof state?.output === 'string' ? { output: publicOutputSnapshot(state.output) } : {}),
56
+ ...(typeof state?.output === 'string' ? { output: publicToolOutput(state.output) } : {}),
57
57
  ...subagent
58
58
  }
59
59
  };
@@ -77,8 +77,8 @@ function openCodeSubagentProjection(toolName, input, output) {
77
77
  ...(nativeResult === undefined
78
78
  ? {}
79
79
  : {
80
- outputSummary: compactRunnerText(nativeResult.result, 10_000) || null,
81
- output: publicOutputSnapshot(nativeResult.result)
80
+ outputSummary: null,
81
+ output: publicToolOutput(nativeResult.result)
82
82
  })
83
83
  };
84
84
  }
@@ -179,6 +179,7 @@ export function createNodeAgentNetwork(options) {
179
179
  cwd: parent?.cwd ?? workspace.path,
180
180
  content: input.content,
181
181
  clientMessageId: input.clientMessageId,
182
+ __suppressNotifications: true,
182
183
  ...(!terminal && parent ? { __marctlOrigin: origin(parent) } : {}),
183
184
  ...(input.title === undefined ? {} : { title: input.title }),
184
185
  ...(model ? { model } : {}),
@@ -194,6 +195,7 @@ export function createNodeAgentNetwork(options) {
194
195
  objective: input.goal.kind === 'set' ? input.goal.objective : '',
195
196
  secretEnvironment: childEnvironment(actor),
196
197
  messageContent: input.content,
198
+ suppressNotifications: true,
197
199
  ...(!terminal && parent ? { messageOrigin: origin(parent) } : {}),
198
200
  clientMessageId: input.clientMessageId,
199
201
  ...(input.attachments === undefined ? {} : { attachments: input.attachments }),
@@ -233,6 +235,7 @@ export function createNodeAgentNetwork(options) {
233
235
  session: current,
234
236
  objective: change.objective,
235
237
  executionContext,
238
+ suppressNotifications: true,
236
239
  secretEnvironment: childEnvironment(actor),
237
240
  ...(actor.requestUserId === undefined ? {} : { initiatedByUserId: actor.requestUserId }),
238
241
  ...(actor.personalInstructions === undefined
@@ -263,6 +266,7 @@ export function createNodeAgentNetwork(options) {
263
266
  ...input,
264
267
  ...(signal ? { [SESSION_ADMISSION_SIGNAL]: signal } : {}),
265
268
  sessionId: session.id,
269
+ __suppressNotifications: true,
266
270
  ...(parent ? { __marctlOrigin: origin(parent) } : {})
267
271
  }));
268
272
  return publicReceipt(result);
@@ -1,4 +1,4 @@
1
- import { appendExecutionPresentation, permissionStopSchema } from '@myagentroam/protocol';
1
+ import { appendToolOutput, retainExecutionInputTruncation, toolOutputSchema, permissionStopSchema } from '@myagentroam/protocol';
2
2
  import { projectConversationWorkProcesses } from './work-process.js';
3
3
  function isRecord(value) {
4
4
  return typeof value === 'object' && value !== null;
@@ -292,9 +292,16 @@ export function mergeConversationItemPayload(existing, next) {
292
292
  }
293
293
  if (next.append === 'output' &&
294
294
  isRecord(next.payload.output) &&
295
- typeof next.payload.output.text === 'string') {
295
+ Array.isArray(next.payload.output.blocks)) {
296
+ const update = toolOutputSchema.parse(next.payload.output);
297
+ const prior = base.output === undefined ? undefined : toolOutputSchema.parse(base.output);
298
+ value.output = appendToolOutput(prior, update);
296
299
  if (next.payload.toolPresentation !== undefined)
297
- value.toolPresentation = appendExecutionPresentation(base.toolPresentation, next.payload.toolPresentation);
300
+ value.toolPresentation = retainExecutionInputTruncation(base.toolPresentation, next.payload.toolPresentation);
301
+ }
302
+ if (next.append === 'output' &&
303
+ isRecord(next.payload.output) &&
304
+ typeof next.payload.output.text === 'string') {
298
305
  const prior = isRecord(base.output) && typeof base.output.text === 'string' ? base.output : undefined;
299
306
  const text = `${prior?.text ?? ''}${next.payload.output.text}`;
300
307
  value.output = {
@@ -107,6 +107,23 @@ export async function readRestrictedDiff(workspacePath, fromRef, toRef) {
107
107
  }
108
108
  return { binary: false, text: result.text, truncated: result.truncated };
109
109
  }
110
+ /** Fetches refs only; failures never expose remote URLs or authentication diagnostics. */
111
+ export async function fetchGitRepository(workspacePath, timeoutMs) {
112
+ if (timeoutMs <= 0)
113
+ return false;
114
+ try {
115
+ const result = await runGitAllowFailure(workspacePath, ['fetch', '--all', '--quiet', '--no-recurse-submodules', '--no-write-fetch-head'], {
116
+ timeoutMs,
117
+ processGroup: true,
118
+ includeStderrInTimeout: false,
119
+ environment: { GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' }
120
+ });
121
+ return result.code === 0;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
110
127
  function currentBranchFromStatusHeader(header) {
111
128
  const value = header.slice(3);
112
129
  if (value.startsWith('HEAD '))
@@ -898,10 +915,12 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
898
915
  cwd: workspacePath,
899
916
  shell: false,
900
917
  windowsHide: true,
918
+ detached: options.processGroup === true && process.platform !== 'win32',
901
919
  stdio: ['ignore', 'pipe', 'pipe'],
902
920
  env: {
903
921
  ...process.env,
904
922
  ...gitOperationEnvironment.getStore(),
923
+ ...options.environment,
905
924
  ...(options.readOnly === true ? { GIT_OPTIONAL_LOCKS: '0' } : {})
906
925
  }
907
926
  });
@@ -911,6 +930,7 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
911
930
  let errorSize = 0;
912
931
  let truncated = false;
913
932
  let timedOut = false;
933
+ let forcedTerminationTimer;
914
934
  const timer = setTimeout(() => {
915
935
  timedOut = true;
916
936
  if (process.platform === 'win32') {
@@ -932,6 +952,26 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
932
952
  });
933
953
  return;
934
954
  }
955
+ if (options.processGroup === true && child.pid !== undefined) {
956
+ const pid = child.pid;
957
+ try {
958
+ // Give Git a chance to release transaction locks before bounded escalation.
959
+ process.kill(-pid, 'SIGTERM');
960
+ forcedTerminationTimer = setTimeout(() => {
961
+ try {
962
+ process.kill(-pid, 'SIGKILL');
963
+ }
964
+ catch {
965
+ // The process group may have completed its cleanup already.
966
+ }
967
+ }, 250);
968
+ forcedTerminationTimer.unref();
969
+ return;
970
+ }
971
+ catch {
972
+ // The group may have exited before the timeout fired.
973
+ }
974
+ }
935
975
  child.kill('SIGKILL');
936
976
  }, options.timeoutMs ?? GIT_TIMEOUT_MS);
937
977
  child.stdout.on('data', (chunk) => {
@@ -953,6 +993,7 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
953
993
  });
954
994
  child.once('error', (error) => {
955
995
  clearTimeout(timer);
996
+ clearTimeout(forcedTerminationTimer);
956
997
  logNodeCommandFailure(`git ${args[0] ?? 'unknown'}`, {
957
998
  reason: error.code ?? 'START_FAILED'
958
999
  });
@@ -960,9 +1001,11 @@ async function runGitAllowFailure(workspacePath, args, options = {}) {
960
1001
  });
961
1002
  child.once('close', (code) => {
962
1003
  clearTimeout(timer);
1004
+ clearTimeout(forcedTerminationTimer);
963
1005
  const stderr = Buffer.concat(errors).toString('utf8');
964
1006
  if (timedOut) {
965
- reject(gitCommandFailure(code === null || code === 0 ? 1 : code, `Git command timed out after ${options.timeoutMs ?? GIT_TIMEOUT_MS} ms.${stderr ? `\n${stderr}` : ''}`, args[0] ?? 'unknown', Math.round(performance.now() - startedAt), 'TIMEOUT'));
1007
+ const details = options.includeStderrInTimeout === false ? '' : stderr;
1008
+ reject(gitCommandFailure(code === null || code === 0 ? 1 : code, `Git command timed out after ${options.timeoutMs ?? GIT_TIMEOUT_MS} ms.${details ? `\n${details}` : ''}`, args[0] ?? 'unknown', Math.round(performance.now() - startedAt), 'TIMEOUT'));
966
1009
  return;
967
1010
  }
968
1011
  const output = Buffer.concat(chunks);
@@ -2,7 +2,7 @@ import { lstat } from 'node:fs/promises';
2
2
  import { basename, posix, resolve } from 'node:path';
3
3
  import { publicErrorMessage } from '@myagentroam/protocol';
4
4
  import { nodeLog } from '../operational.js';
5
- import { GitCommandError, isWorkspaceChangeVisible, listGitWorktrees, listGitSubmodules, readCurrentChangesSummary, resolveGitRepository, resolveWorkspaceGitRoot } from './git.js';
5
+ import { GitCommandError, fetchGitRepository, isWorkspaceChangeVisible, listGitWorktrees, listGitSubmodules, readCurrentChangesSummary, resolveGitRepository, resolveWorkspaceGitRoot } from './git.js';
6
6
  const CACHE_TTL_MS = 5_000;
7
7
  const REPOSITORY_READ_CONCURRENCY = 4;
8
8
  const WORKTREE_READ_CONCURRENCY = 4;
@@ -27,6 +27,44 @@ export class WorkspaceChangeService {
27
27
  this.changesCache.clear();
28
28
  this.changesVersions.clear();
29
29
  }
30
+ async fetch(workspace, repositoryPath) {
31
+ if (workspace.kind !== 'GIT_WORKSPACE')
32
+ throw new Error('WORKSPACE_NOT_GIT');
33
+ const root = await resolveWorkspaceGitRoot(workspace.path, workspace.includeParentRepository ?? true);
34
+ // A selected History repository is validated before any network operation.
35
+ const selected = repositoryPath === undefined ? undefined : await resolveGitRepository(root, repositoryPath);
36
+ let paths;
37
+ if (selected !== undefined)
38
+ paths = [repositoryPath];
39
+ else {
40
+ this.invalidate(workspace.id);
41
+ const current = (await this.current(workspace));
42
+ // Linked worktrees share the root's refs and must not fetch them twice.
43
+ paths = current.repositories
44
+ .filter((entry) => entry.source !== 'WORKTREE' && entry.status !== 'UNAVAILABLE')
45
+ .map((entry) => entry.repositoryPath);
46
+ }
47
+ const deadline = performance.now() + 10_000;
48
+ try {
49
+ const results = await mapWithConcurrency(paths, REPOSITORY_READ_CONCURRENCY, async (path) => {
50
+ try {
51
+ if (performance.now() >= deadline)
52
+ return false;
53
+ const repository = selected ?? (await resolveGitRepository(root, path));
54
+ return await fetchGitRepository(repository, Math.max(0, Math.floor(deadline - performance.now())));
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ });
60
+ return { fetchFailed: results.some((success) => !success) };
61
+ }
62
+ finally {
63
+ // Fetch is request/user scoped, never coalesced with another user's credentials.
64
+ // Invalidate even after partial failure: some remotes may already have advanced.
65
+ this.invalidate(workspace.id);
66
+ }
67
+ }
30
68
  async current(workspace) {
31
69
  if (workspace.kind !== 'GIT_WORKSPACE')
32
70
  throw new Error('WORKSPACE_NOT_GIT');
@@ -58,6 +58,7 @@ export class WorkspaceWorkbenchService {
58
58
  'workspace.files.upload.complete': (data) => this.completeUpload(record(data)),
59
59
  'workspace.files.upload.cancel': (data) => this.cancelUpload(record(data)),
60
60
  'workspace.changes.current': (data) => this.currentChanges(record(data)),
61
+ 'workspace.git.fetch': (data) => this.gitFetch(record(data)),
61
62
  'workspace.change.diff': (data) => this.changeDiff(record(data)),
62
63
  'workspace.change.restore': (data) => this.restoreChange(record(data)),
63
64
  'workspace.git.refs.list': (data) => this.gitRefs(record(data)),
@@ -239,6 +240,13 @@ export class WorkspaceWorkbenchService {
239
240
  async gitRefs(input) {
240
241
  return { refs: await listGitHistoryRefs(await this.gitRepository(input)) };
241
242
  }
243
+ async gitFetch(input) {
244
+ if (typeof input.workspaceId !== 'string')
245
+ throw new Error('WORKSPACE_NOT_FOUND');
246
+ if (input.repositoryPath !== undefined && typeof input.repositoryPath !== 'string')
247
+ throw new Error('GIT_REPOSITORY_INVALID');
248
+ return this.options.changes.fetch(this.gitWorkspace(input.workspaceId), input.repositoryPath);
249
+ }
242
250
  async gitHistory(input) {
243
251
  const refs = this.refs(input);
244
252
  if (input.cursor !== undefined && typeof input.cursor !== 'string')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.11.44",
3
+ "version": "0.11.45",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -38,10 +38,10 @@
38
38
  "sharp": "0.35.5",
39
39
  "ws": "^8.21.3",
40
40
  "zod": "4.4.3",
41
- "@myagentroam/agent": "0.11.44",
41
+ "@myagentroam/agent": "0.11.45",
42
+ "@myagentroam/code-navigation": "0.11.1",
42
43
  "@myagentroam/marctl": "0.11.6",
43
- "@myagentroam/protocol": "0.11.44",
44
- "@myagentroam/code-navigation": "0.11.1"
44
+ "@myagentroam/protocol": "0.11.45"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/ws": "^8.18.1"