@myagentroam/node 0.10.49 → 0.10.51

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/connector.js CHANGED
@@ -856,37 +856,8 @@ export class NodeConnector {
856
856
  retainEnvironment: (environment) => this.gitExecution().retain(environment),
857
857
  goalCurrent: (input) => input.goal === undefined || this.goalGenerationCurrent(input.sessionId, input.goal),
858
858
  goalActive: (sessionId) => this.goalRuntime.get(sessionId)?.status === 'active',
859
- afterExecutionStarted: async (input) => {
860
- if (input.goal === undefined)
861
- return;
862
- if (!this.goalGenerationCurrent(input.sessionId, input.goal))
863
- return;
864
- const updated = this.goalRuntime.recordTurn(input.sessionId);
865
- const session = this.runtime.getAgentSession(input.sessionId);
866
- const run = this.runtime.getRun(input.runId);
867
- await this.enqueueGoalContinuation({
868
- sessionId: input.sessionId,
869
- goal: { id: input.goal.id, revision: input.goal.revision },
870
- ...(run?.initiatedByUserId === undefined
871
- ? {}
872
- : { initiatedByUserId: run.initiatedByUserId }),
873
- ...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
874
- secretEnvironment: input.secretEnvironment,
875
- ...(input.personalInstructions === undefined
876
- ? {}
877
- : { personalInstructions: input.personalInstructions }),
878
- ...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
879
- ...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
880
- ...(input.marAgentCompact === undefined
881
- ? {}
882
- : { marAgentCompact: input.marAgentCompact }),
883
- ...(input.marAgentRuntimeScopeId === undefined
884
- ? {}
885
- : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
886
- });
887
- if (updated !== undefined && session !== undefined)
888
- this.publishGoalState(session, updated);
889
- },
859
+ afterExecutionStarted: (input) => this.maintainGoalContinuation(input, true),
860
+ afterExecutionSteered: (input) => this.maintainGoalContinuation(input, false),
890
861
  afterExecutionStartFailed: (input, error) => {
891
862
  nodeLog('goal.execution-start.failed', {
892
863
  sessionId: input.sessionId,
@@ -1963,6 +1934,34 @@ export class NodeConnector {
1963
1934
  current.id === goal.id &&
1964
1935
  current.revision === goal.revision);
1965
1936
  }
1937
+ async maintainGoalContinuation(input, started) {
1938
+ if (this.stopped || input.goal === undefined)
1939
+ return;
1940
+ if (!this.goalGenerationCurrent(input.sessionId, input.goal))
1941
+ return;
1942
+ const updated = started ? this.goalRuntime.recordTurn(input.sessionId) : undefined;
1943
+ const session = this.runtime.getAgentSession(input.sessionId);
1944
+ const run = this.runtime.getRun(input.runId);
1945
+ await this.enqueueGoalContinuation({
1946
+ sessionId: input.sessionId,
1947
+ goal: { id: input.goal.id, revision: input.goal.revision },
1948
+ continuationEpoch: input.goal.epoch,
1949
+ ...(run?.initiatedByUserId === undefined ? {} : { initiatedByUserId: run.initiatedByUserId }),
1950
+ ...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
1951
+ secretEnvironment: input.secretEnvironment,
1952
+ ...(input.personalInstructions === undefined
1953
+ ? {}
1954
+ : { personalInstructions: input.personalInstructions }),
1955
+ ...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
1956
+ ...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
1957
+ ...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
1958
+ ...(input.marAgentRuntimeScopeId === undefined
1959
+ ? {}
1960
+ : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
1961
+ });
1962
+ if (updated !== undefined && session !== undefined)
1963
+ this.publishGoalState(session, updated);
1964
+ }
1966
1965
  handleNativeGoalUpdate(runId, nativeGoal) {
1967
1966
  const pending = this.continuousRunCoordinator.pending(runId);
1968
1967
  if (pending?.goal === undefined || !this.goalGenerationCurrent(pending.sessionId, pending.goal))
@@ -10,14 +10,14 @@ export function marctlHostSkill(environment = {}) {
10
10
  return undefined;
11
11
  return {
12
12
  name: 'marctl',
13
- description: 'You are in a MAR-managed Workspace with marctl on PATH. Use this skill to inspect, create, rename, configure, message, stop or coordinate independent Sessions, manage their scheduled tasks, or use authorized Node terminals and file transfer. Load before cross-Session coordination.',
13
+ description: 'You are in a MAR-managed Workspace with marctl on PATH. Load this skill to create, coordinate, message or stop independent Sessions; inspect Session status/history/results; change titles or models; manage Goals or scheduled tasks for the current or other Sessions; or run commands/transfer files on other authorized MAR Nodes, even when marctl is not named. Ordinary local shell work and built-in subagents alone do not require it.',
14
14
  requiredTools: ['exec'],
15
15
  load: async () => marctlInstructions
16
16
  };
17
17
  }
18
18
  const marctlInstructions = [
19
19
  '## MAR Workspace CLI',
20
- 'Use exec to run marctl. Reuse loaded guidance while it remains in context. Consult local marctl help sessions for unclear options and marctl --version when version matters; neither needs a network request. Capabilities and permissions still apply.',
20
+ 'Use exec to run marctl. Reuse loaded guidance while it remains in context. Consult local marctl help sessions for unclear options (shared by all Session subcommands) and marctl --version when version matters; neither needs a network request. Capabilities and permissions still apply.',
21
21
  'Use sessions current --json only when the current identity or scope is unknown. For a known target ID, use sessions show <session-id> --json only when fresh state is needed, without current/list first. Use sessions list --limit 10 --json to discover an unknown target; narrow with --query <title> or --parent <session-id>. These query Node-known Sessions without scanning Runners. Use sessions graph <session-id> --json only for a creation tree (bounded to 100 nodes with truncated); start automatically records the current Session as parent, not task ownership. Stop once found and follow nextCursor only if needed. Do not scan the Workspace to prove capacity: check assigned work, recorded worker IDs and overlapping write scopes.',
22
22
  'MAR binds sessions to the current Node and Workspace. No --node or --workspace override exists. Use returned native Session IDs, not titles, for later operations. A Workspace terminal may have no current Session. Missing scope, an old Node, expired authorization or an unavailable Runner is an explicit error, not permission to bypass the API.',
23
23
  '## Create and coordinate independent Sessions',
@@ -29,7 +29,7 @@ const marctlInstructions = [
29
29
  'show --json fields: session.activity, session.model, session.effort, session.customTitle or session.runnerTitle, runs[] (id/status), queue.items[], and goal.status (goal may be null). Run status and Goal status are separate.',
30
30
  'Rename any authorized Workspace Session with sessions rename <session-id> --title <name> --json; parent relationships do not restrict renaming.',
31
31
  'show/config/history/graph/rename and schedule get/create/update may omit the Session ID to use the current Session. Without a current Session, specify an ID. send/goal/stop/wait always require explicit target IDs.',
32
- 'Use sessions schedule list/get/create/update for the same tasks as the built-in schedule MCP, which is hidden while this skill is available. list is Workspace-scoped. create requires --cron and text (-m/--message-file, up to 20,000 characters). update requires --expected-revision from get and at least cron or text. Other-owner edits return a PENDING proposal, not an applied change; scheduled execution is read-only. Follow existing approval rules and do not retry conflicts or uncertain writes blindly.',
32
+ 'Use sessions schedule list/get/create/update for the same tasks as the built-in schedule MCP, which is hidden while this skill is available. list is Workspace-scoped. create requires --cron and text (-m/--message-file, up to 20,000 characters). update requires --expected-revision from get and at least cron or text. get returns task (null if absent), with id, revision, objective, cron, timezone and enabled; applied updates also return task. Reuse that task instead of probing its shape or refetching unchanged state. Schedule text is trimmed at both ends; compare after the same trimming; a conclusive update response needs no extra get. Other-owner edits return a PENDING proposal, not an applied change; scheduled execution is read-only. Follow existing approval rules and do not retry conflicts or uncertain writes blindly.',
33
33
  'Read results with sessions history <session-id> --limit 10 --json (default: latest 10 SEGMENTs, not messages; --limit 1..100). In segments[].items[], select kind === "assistant_message" and read payload.text; keep segment runId and user_message.payload.clientMessageId for queued-input correlation. Reuse the returned page; follow --cursor <nextCursor> only for missing evidence.',
34
34
  'Use sessions send <session-id> --steer -m "Additional context: focus on the resume path." --json for decision-changing updates rather than routine reports: it may switch the current execution. Node decides delivery from actual state; an idle Session receives ordinary input. Choose exactly one of --steer or --queue, even when idle; missing, conflicting, repeated, or obsolete options are rejected locally before input is read or a request is sent. Use --queue to place input in a subsequent turn; with an active Goal, ordinary queued input waits until the Goal exits. start/send return clientMessageId and the accepted/queued result. A queued result may refer to the currently active Run: associate queueItemId/clientMessageId with history before deciding that input finished. There are no queue-item edit/delete/reorder commands; show includes queue state.',
35
35
  'Wait with sessions wait <session-id> --run <run-id> --timeout 5m --json through the existing Watch. Prefer --timeout 5m when blocked on task completion; use 30s for a short deadline. Continue useful independent work before waiting. If exec returns a running process or cell, reuse its process/cell handle rather than starting another wait or polling show/list. Timeout or cancelled waiting does not stop the task. Use sessions stop <session-id> --run <run-id> --json only to stop that active Run; ownership rules still apply.',
@@ -121,7 +121,7 @@ export class ContinuousRunCoordinator {
121
121
  const attempt = this.tryNativeSteer(input.submissionId, active, input.onNativeAccepted);
122
122
  if (attempt)
123
123
  return attempt.then((accepted) => accepted
124
- ? 'RESTARTING'
124
+ ? 'ACCEPTED'
125
125
  : this.suspended || this.options.stopped()
126
126
  ? 'QUEUED'
127
127
  : this.settleFallback(input));
@@ -166,7 +166,7 @@ export class ContinuousRunCoordinator {
166
166
  this.options.runtime.prioritizeQueueItem(item.id);
167
167
  this.publish(sessionId);
168
168
  this.scheduleImmediate(sessionId);
169
- return 'RESTARTING';
169
+ return 'ACCEPTED';
170
170
  }
171
171
  if (this.options.runtime.currentExecutionId(active.id) === undefined &&
172
172
  !this.isMessageRun(active)) {
@@ -176,7 +176,7 @@ export class ContinuousRunCoordinator {
176
176
  return 'QUEUED';
177
177
  }
178
178
  this.replace(input.submissionId, active.id);
179
- return 'RESTARTING';
179
+ return 'ACCEPTED';
180
180
  }
181
181
  if (startsAcceptedRun) {
182
182
  this.schedule(sessionId);
@@ -214,7 +214,7 @@ export class ContinuousRunCoordinator {
214
214
  const attempt = this.tryNativeSteer(itemId, active);
215
215
  if (attempt)
216
216
  return attempt.then((accepted) => accepted
217
- ? { run: active, delivery: 'RESTARTING' }
217
+ ? { run: active, delivery: 'ACCEPTED' }
218
218
  : this.suspended || this.options.stopped()
219
219
  ? { run: active, delivery: 'QUEUED' }
220
220
  : this.executeQueueItemFallback(itemId));
@@ -247,15 +247,31 @@ export class ContinuousRunCoordinator {
247
247
  return undefined;
248
248
  }
249
249
  const task = attempt
250
- .then((result) => {
250
+ .then(async (result) => {
251
251
  if (result.status === 'notAccepted')
252
252
  return false;
253
253
  onAccepted?.();
254
254
  this.options.runtime.consumeSteeredQueueItem(itemId, active.id);
255
- this.remove(itemId);
256
255
  this.immediateQueueItems.delete(itemId);
257
256
  this.options.runtime.resumeSessionQueue(item.sessionId);
258
257
  this.options.clearImages(itemId);
258
+ if (prepared.goal !== undefined && this.goalCurrent(prepared)) {
259
+ const current = this.preparedInputs.get(executionId);
260
+ if (current !== undefined)
261
+ this.preparedInputs.set(executionId, { ...current, goal: prepared.goal });
262
+ }
263
+ try {
264
+ await this.options.afterExecutionSteered?.({ ...prepared, runId: executionId });
265
+ }
266
+ catch (error) {
267
+ nodeLog('queue.native_steer.continuation_failed', {
268
+ level: 'warn',
269
+ sessionId: item.sessionId,
270
+ executionId,
271
+ code: safeErrorCode(error, 'NATIVE_STEER_CONTINUATION_FAILED')
272
+ });
273
+ }
274
+ this.remove(itemId);
259
275
  // Publication is best effort after ownership transfers; it cannot undo SDK acceptance.
260
276
  for (const notify of [
261
277
  () => {
@@ -301,7 +317,7 @@ export class ContinuousRunCoordinator {
301
317
  if (this.isMessageRun(active))
302
318
  this.scheduleImmediate(item.sessionId);
303
319
  }
304
- return { run: active, delivery: 'RESTARTING' };
320
+ return { run: active, delivery: 'ACCEPTED' };
305
321
  }
306
322
  this.startIdleQueue(item.sessionId);
307
323
  const started = this.options.runtime.activeSessionRun(item.sessionId);
@@ -591,6 +607,9 @@ export class ContinuousRunCoordinator {
591
607
  for (const [id, pending] of this.preparedInputs) {
592
608
  if (id === preservedSubmissionId)
593
609
  continue;
610
+ // Native admission owns receiving items until its receipt settles.
611
+ if (this.steerInFlight.has(id))
612
+ continue;
594
613
  if (pending.sessionId !== sessionId || pending.goal === undefined)
595
614
  continue;
596
615
  if (pending.goal.id === preservedGoal?.id && pending.goal.revision === preservedGoal.revision)
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { link, lstat, mkdir, open, readFile, rename, rm, stat } from 'node:fs/promises';
2
+ import { link, lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
3
3
  import { dirname, join, posix, resolve } from 'node:path';
4
4
  import { parseComposerAttachmentUpload, validComposerAttachmentName } from '../util/node-operation-parsers.js';
5
5
  import { preflightWorkspaceUpload, workspaceUploadTemporaryName } from '../workspace.js';
@@ -61,9 +61,7 @@ export class WorkspaceUploadService {
61
61
  throw new Error('UPLOAD_TARGET_BUSY');
62
62
  const id = randomUUID();
63
63
  const temporaryPath = resolve(dirname(target.absolutePath), workspaceUploadTemporaryName(id));
64
- const parentIdentity = await stat(dirname(target.absolutePath));
65
64
  const handle = await open(temporaryPath, 'wx+');
66
- const stagingIdentity = await handle.stat();
67
65
  const timer = setTimeout(() => {
68
66
  const upload = this.state.uploads.get(id);
69
67
  if (upload !== undefined)
@@ -73,8 +71,6 @@ export class WorkspaceUploadService {
73
71
  id,
74
72
  workspaceId: workspace.id,
75
73
  workspacePath: workspace.path,
76
- parentIdentity,
77
- stagingIdentity,
78
74
  path: target.path,
79
75
  targetPath: target.absolutePath,
80
76
  temporaryPath,
@@ -361,15 +357,10 @@ export class WorkspaceUploadService {
361
357
  throw new Error('UPLOAD_TARGET_EXISTS');
362
358
  }
363
359
  const parent = await lstat(dirname(upload.targetPath));
364
- if (!parent.isDirectory() ||
365
- parent.isSymbolicLink() ||
366
- !sameIdentity(parent, upload.parentIdentity))
360
+ if (!parent.isDirectory() || parent.isSymbolicLink())
367
361
  throw new Error('UPLOAD_TARGET_INVALID');
368
362
  const staging = await lstat(upload.temporaryPath);
369
- if (!staging.isFile() ||
370
- staging.isSymbolicLink() ||
371
- !sameIdentity(staging, upload.stagingIdentity) ||
372
- staging.size !== upload.size)
363
+ if (!staging.isFile() || staging.isSymbolicLink() || staging.size !== upload.size)
373
364
  throw new Error('UPLOAD_STAGING_INVALID');
374
365
  }
375
366
  async remove(upload) {
@@ -382,9 +373,9 @@ export class WorkspaceUploadService {
382
373
  return undefined;
383
374
  throw error;
384
375
  });
385
- if (!sameIdentity(parent, upload.parentIdentity) ||
376
+ if (!parent.isDirectory() ||
386
377
  parent.isSymbolicLink() ||
387
- (staging !== undefined && !sameIdentity(staging, upload.stagingIdentity)))
378
+ (staging !== undefined && (!staging.isFile() || staging.isSymbolicLink())))
388
379
  throw new Error('UPLOAD_CLEANUP_FAILED');
389
380
  if (staging !== undefined)
390
381
  await rm(upload.temporaryPath, { force: true });
@@ -427,9 +418,6 @@ export class WorkspaceUploadService {
427
418
  };
428
419
  }
429
420
  }
430
- function sameIdentity(actual, expected) {
431
- return actual.dev === expected.dev && actual.ino === expected.ino;
432
- }
433
421
  async function safeDirectory(path) {
434
422
  const value = await lstat(path);
435
423
  if (!value.isDirectory() || value.isSymbolicLink())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.10.49",
3
+ "version": "0.10.51",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "playwright": "1.62.1",
38
38
  "ws": "^8.21.3",
39
39
  "zod": "4.4.3",
40
- "@myagentroam/agent": "0.10.49",
40
+ "@myagentroam/agent": "0.10.51",
41
41
  "@myagentroam/code-navigation": "0.10.1",
42
- "@myagentroam/marctl": "0.10.10",
43
- "@myagentroam/protocol": "0.10.49"
42
+ "@myagentroam/protocol": "0.10.51",
43
+ "@myagentroam/marctl": "0.10.10"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"