@myagentroam/node 0.10.32 → 0.10.33

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/database.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { NODE_SCHEMA_MIGRATION_V17 } from './migrations/v017.js';
2
+ import { NODE_SCHEMA_MIGRATION_V18 } from './migrations/v018.js';
3
+ import { normalizeSessionPrompt } from './util/personal-instructions.js';
2
4
  import { randomUUID } from 'node:crypto';
3
5
  import { closeSync, existsSync, mkdirSync, openSync, rmSync } from 'node:fs';
4
6
  import { dirname } from 'node:path';
@@ -16,7 +18,8 @@ export const NODE_SCHEMA_MIGRATIONS = [
16
18
  NODE_SCHEMA_MIGRATION_V14,
17
19
  NODE_SCHEMA_MIGRATION_V15,
18
20
  NODE_SCHEMA_MIGRATION_V16,
19
- NODE_SCHEMA_MIGRATION_V17
21
+ NODE_SCHEMA_MIGRATION_V17,
22
+ NODE_SCHEMA_MIGRATION_V18
20
23
  ];
21
24
  const LEGACY_NODE_SCHEMA_V12 = NODE_SCHEMA_VERSION_V13 - 1;
22
25
  const OBSOLETE_NODE_SCHEMA_V16 = 16;
@@ -266,6 +269,9 @@ export class NodeDatabase {
266
269
  const runnerTitle = input.runnerTitle === undefined
267
270
  ? (current?.runnerTitle ?? null)
268
271
  : input.runnerTitle?.trim() || null;
272
+ const sessionPrompt = input.sessionPrompt === undefined
273
+ ? (current?.sessionPrompt ?? null)
274
+ : normalizeSessionPrompt(input.sessionPrompt);
269
275
  const runnerTitlePending = input.runnerTitlePending === undefined
270
276
  ? (current?.runnerTitlePending ?? false)
271
277
  : input.runnerTitlePending;
@@ -285,6 +291,7 @@ export class NodeDatabase {
285
291
  const metadata = JSON.stringify({
286
292
  version: 1,
287
293
  customTitle,
294
+ sessionPrompt,
288
295
  runnerTitle,
289
296
  runnerTitlePending,
290
297
  model,
@@ -321,6 +328,7 @@ export class NodeDatabase {
321
328
  }
322
329
  return {
323
330
  customTitle,
331
+ sessionPrompt,
324
332
  runnerTitle,
325
333
  runnerTitlePending,
326
334
  pinnedAt: pinOrder === null ? null : 1,
@@ -564,6 +572,7 @@ function sessionMetadata(row) {
564
572
  const value = (key) => typeof raw[key] === 'string' ? raw[key] : null;
565
573
  return {
566
574
  customTitle: value('customTitle'),
575
+ sessionPrompt: value('sessionPrompt'),
567
576
  runnerTitle: value('runnerTitle'),
568
577
  runnerTitlePending: raw['runnerTitlePending'] === true,
569
578
  model: value('model'),
@@ -0,0 +1,9 @@
1
+ import { NODE_SCHEMA_MIGRATION_V17 } from './v017.js';
2
+ export const NODE_SCHEMA_MIGRATION_V18 = {
3
+ version: 18,
4
+ sql: `UPDATE agent_sessions
5
+ SET metadata = json_set(metadata, '$.sessionPrompt', NULL)
6
+ WHERE json_type(metadata, '$.sessionPrompt') IS NULL;`,
7
+ preflight: (database) => NODE_SCHEMA_MIGRATION_V17.assert(database),
8
+ assert: (database) => NODE_SCHEMA_MIGRATION_V17.assert(database)
9
+ };
@@ -17,15 +17,16 @@ export function marctlHostSkill(environment = {}) {
17
17
  }
18
18
  const marctlInstructions = [
19
19
  '## MAR Workspace CLI',
20
- 'Use exec to run marctl. Start with marctl sessions current --json and marctl sessions list --json. Local marctl help sessions and marctl --version work without a network request. Read the installed help for exact options; 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 and marctl --version when version matters; neither needs a network request. Capabilities and permissions still apply.',
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; list has no name-search option. 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.',
21
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.',
22
23
  '## Create and coordinate independent Sessions',
23
- 'Use -m / --message for direct text, or --message-file for a UTF-8 file (- reads stdin); these options are mutually exclusive. Create a Session with marctl sessions start --runner codex --title "fixer#42 Handle empty totals" --message-file task.txt --json. Other Runner names are mar-agent, claude-code and opencode. An omitted model uses the target Runner configuration, not the Coordinator model.',
24
+ 'Prefer -m / --message for short text; use --message-file for longer UTF-8 text or an existing file (- reads stdin). These options are mutually exclusive. Example: marctl sessions start --runner codex --title "fixer#42 Handle empty totals" -m "Implement issue #42 and report test evidence" --json. Other Runner names are mar-agent, claude-code and opencode. An omitted model uses the target Runner configuration, not the Coordinator model.',
24
25
  'For start/send, repeat --image <local-path> to attach PNG, JPEG, GIF or WebP images, with or without text. Images are read on this CLI host, not by remote path. Limits: 5 images, 5 MiB each, 10 MiB total. Actual Runner/model image capabilities apply; goal set accepts text only.',
25
- 'Create workers only as tasks need them. For Fixer/Reviewer work use fixer#<task-id> <description> and reviewer#<task-or-change-id> <description>. These are naming conventions, not product roles or a fixed worker pool. Put role, scope, acceptance criteria and delivery requirements in the first ordinary message. There is no Session custom prompt setting.',
26
- 'Use sessions show <session-id> --json for Run, queue and Goal state. Read bounded visible history with sessions history <session-id> --limit 10 --json; follow nextCursor only when needed. Hidden reasoning, credentials and raw model requests are not collaboration artifacts.',
27
- 'Send with sessions send <session-id> --message-file followup.txt --json. Default send does not interrupt. Use --delivery queue or replace-current only when intended. start/send return clientMessageId and the existing accepted/queued result. A queued result may refer to the currently active Run: associate queueItemId/clientMessageId with history before deciding that input finished.',
28
- 'Wait with sessions wait <session-id> --run <run-id> --timeout 30s --json. This observes that exact Run through the existing Watch. Timeout or cancelled waiting does not stop the task. Use sessions stop <session-id> --run <run-id> --json to stop an explicit active Run; ownership rules still apply.',
26
+ 'Create workers only as tasks need them, named fixer#<task-id> <description> or reviewer#<task-or-change-id> <description>. These are conventions, not product roles or a fixed pool. Put role, scope, acceptance criteria and delivery requirements in the first message. Session Prompt is edited in the Web UI only; marctl has no editing command, and sends to existing Sessions use normal prompt injection.',
27
+ 'Reuse recorded Session/Run IDs and receipts. Refresh volatile Run, queue and Goal state with sessions show only when the next action depends on it. For results use sessions history <session-id> --limit 10 --json; follow nextCursor only for missing evidence. Select needed JSON fields and relevant assistant results instead of dumping full Session lists or tool transcripts. Hidden reasoning, credentials and raw model requests are not collaboration artifacts.',
28
+ 'Send with sessions send <session-id> -m "Review the updated patch" --json. Default send does not interrupt and can resume a paused queue; --delivery queue preserves a paused queue. Otherwise eligible queued input can start when idle. --delivery replace-current cancels the current Run and prioritizes the new input after release; it is not live steering. There are no queue-item edit/delete/reorder commands; show includes queue state. 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.',
29
+ '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.',
29
30
  'Goal get/set/clear use the existing Session Goal semantics; see marctl help sessions. A Coordinator can work from ordinary messages or its existing schedule without a Goal. Worker completion never automatically wakes it. Actively wait/read results, or finish and reconcile on the next user message or scheduled input.',
30
31
  '## Verify, recover and respect authorization',
31
32
  'An accepted message or SUCCEEDED Run is not proof of business completion. Inspect artifacts and test evidence. If review finds a defect, send a focused correction to the recorded Fixer Session, then request another review with the recorded Reviewer ID.',
@@ -78,6 +78,7 @@ export class NodeRuntimeState {
78
78
  effort: input.effort ?? null,
79
79
  access: input.access ?? null,
80
80
  customTitle,
81
+ sessionPrompt: input.sessionPrompt ?? null,
81
82
  runnerTitle: input.title?.trim().slice(0, 160) || null,
82
83
  pinnedAt: null,
83
84
  // A first user prompt is only a process-local temporary title. A
@@ -112,6 +113,7 @@ export class NodeRuntimeState {
112
113
  effort: null,
113
114
  access: null,
114
115
  customTitle: null,
116
+ sessionPrompt: null,
115
117
  runnerTitle: null,
116
118
  pinnedAt: null,
117
119
  titleSource: 'AUTO',
@@ -275,6 +277,7 @@ export class NodeRuntimeState {
275
277
  throw new Error('SESSION_TITLE_INVALID');
276
278
  return this.replaceSession(current.id, {
277
279
  customTitle: title,
280
+ sessionPrompt: input.sessionPrompt === undefined ? (current.sessionPrompt ?? null) : input.sessionPrompt,
278
281
  pinnedAt: input.pinned === undefined ? current.pinnedAt : input.pinned ? this.now() : null,
279
282
  titleSource: title === null ? 'AUTO' : 'CUSTOM'
280
283
  });
@@ -1,5 +1,6 @@
1
1
  import { withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
2
2
  import { safeErrorCode } from '../util/safe-error.js';
3
+ import { withSessionPrompt } from '../util/personal-instructions.js';
3
4
  import { prepareComposerResourceInput } from './composer-message-input.js';
4
5
  export class ContinuousRunCoordinator {
5
6
  options;
@@ -327,6 +328,7 @@ export class ContinuousRunCoordinator {
327
328
  this.rejectStaleGoal(executionId);
328
329
  return;
329
330
  }
331
+ const personalInstructions = withSessionPrompt(pending.personalInstructions, this.options.runtime.getAgentSession(pending.sessionId)?.sessionPrompt);
330
332
  this.options.control('run.start', {
331
333
  ...(pending.usageGrant === undefined ? {} : { usageGrant: pending.usageGrant }),
332
334
  runId: executionId,
@@ -364,9 +366,7 @@ export class ContinuousRunCoordinator {
364
366
  ...mailMcp?.secrets
365
367
  }
366
368
  }),
367
- ...(pending.personalInstructions === undefined
368
- ? {}
369
- : { personalInstructions: pending.personalInstructions }),
369
+ ...(personalInstructions === undefined ? {} : { personalInstructions }),
370
370
  ...(pending.marAgentModel === undefined ? {} : { marAgentModel: pending.marAgentModel }),
371
371
  ...(pending.marAgentModels === undefined ? {} : { marAgentModels: pending.marAgentModels }),
372
372
  ...(pending.marAgentCompact === undefined
@@ -69,7 +69,8 @@ export class ExternalSessionResumeService {
69
69
  effort: configuration.effort,
70
70
  access: configuration.access,
71
71
  ...(session.runnerTitle === null ? {} : { title: session.runnerTitle }),
72
- ...(session.customTitle === null ? {} : { customTitle: session.customTitle })
72
+ ...(session.customTitle === null ? {} : { customTitle: session.customTitle }),
73
+ sessionPrompt: session.sessionPrompt ?? null
73
74
  });
74
75
  },
75
76
  workspacePath: this.options.workspacePath,
@@ -80,6 +80,7 @@ export class NativeSessionProjectionService {
80
80
  effort: record.metadata.effort,
81
81
  access: record.metadata.access,
82
82
  customTitle: record.metadata.customTitle,
83
+ sessionPrompt: record.metadata.sessionPrompt ?? null,
83
84
  runnerTitle: record.metadata.runnerTitle,
84
85
  pinnedAt: record.pinOrder === null ? null : 1,
85
86
  pinOrder: record.pinOrder,
@@ -112,7 +113,8 @@ export class NativeSessionProjectionService {
112
113
  ...(direct.effort === null ? {} : { effort: direct.effort }),
113
114
  ...(direct.access === null ? {} : { access: direct.access }),
114
115
  ...(direct.runnerTitle === null ? {} : { title: direct.runnerTitle }),
115
- ...(direct.customTitle === null ? {} : { customTitle: direct.customTitle })
116
+ ...(direct.customTitle === null ? {} : { customTitle: direct.customTitle }),
117
+ sessionPrompt: direct.sessionPrompt ?? null
116
118
  });
117
119
  }
118
120
  resumedConfiguration(session) {
@@ -183,6 +185,7 @@ export class NativeSessionProjectionService {
183
185
  effort: metadata?.effort ?? null,
184
186
  access: metadata?.access ?? null,
185
187
  customTitle: metadata?.customTitle ?? null,
188
+ sessionPrompt: metadata?.sessionPrompt ?? null,
186
189
  runnerTitle,
187
190
  pinnedAt: metadata?.pinnedAt ?? null,
188
191
  pinOrder: metadata?.pinOrder ?? null,
@@ -94,6 +94,7 @@ export class SessionCatalogService {
94
94
  effort: persisted.metadata.effort,
95
95
  access: persisted.metadata.access,
96
96
  customTitle: persisted.metadata.customTitle,
97
+ sessionPrompt: persisted.metadata.sessionPrompt ?? null,
97
98
  runnerTitle: persisted.metadata.runnerTitle,
98
99
  pinnedAt: persisted.pinOrder === null ? null : 1,
99
100
  pinOrder: persisted.pinOrder,
@@ -2,6 +2,7 @@ import { parseMessageAttachments, validSessionEffort, validSessionOption, validS
2
2
  import { isPlainRecord } from '../util/node-operation-parsers.js';
3
3
  import { parseSecretEnvironment } from '../util/secret-environment.js';
4
4
  import { ScheduledSessionStore } from './scheduled-session-store.js';
5
+ import { normalizeSessionPrompt } from '../util/personal-instructions.js';
5
6
  export class SessionLifecycleService {
6
7
  options;
7
8
  starts = new Map();
@@ -21,6 +22,7 @@ export class SessionLifecycleService {
21
22
  this.starts.clear();
22
23
  }
23
24
  async updateMetadata(input) {
25
+ const sessionPrompt = input.sessionPrompt === undefined ? undefined : normalizeSessionPrompt(input.sessionPrompt);
24
26
  if (typeof input.sessionId !== 'string' ||
25
27
  (input.customTitle !== undefined &&
26
28
  input.customTitle !== null &&
@@ -35,6 +37,8 @@ export class SessionLifecycleService {
35
37
  (await this.options.projection.createMetadataProjection(input.sessionId));
36
38
  if (current === undefined)
37
39
  throw new Error('SESSION_NOT_FOUND');
40
+ if (sessionPrompt !== undefined && current.externalSessionId === null)
41
+ throw new Error('SESSION_ACTIVATION_REQUIRED');
38
42
  if (input.pinned === false) {
39
43
  const task = new ScheduledSessionStore(this.options.database().raw).get(current.externalSessionId ?? current.id);
40
44
  if (task?.enabled === true)
@@ -43,21 +47,31 @@ export class SessionLifecycleService {
43
47
  const nativeCodexRename = this.options.runners.require(current.runner).usesRunnerTitleForRename() &&
44
48
  current.externalSessionId !== null &&
45
49
  input.customTitle !== undefined;
46
- await this.options.projection.rename(current, {
47
- ...(input.customTitle === undefined ? {} : { title: input.customTitle }),
48
- ...(input.pinned === undefined ? {} : { pinned: input.pinned })
49
- });
50
+ if (input.customTitle !== undefined || input.pinned !== undefined)
51
+ await this.options.projection.rename(current, {
52
+ ...(input.customTitle === undefined ? {} : { title: input.customTitle }),
53
+ ...(input.pinned === undefined ? {} : { pinned: input.pinned })
54
+ });
55
+ if (sessionPrompt !== undefined && current.externalSessionId !== null)
56
+ this.options.database().saveNativeSessionMetadata({
57
+ runner: current.runner,
58
+ externalSessionId: current.externalSessionId,
59
+ workspaceId: current.workspaceId,
60
+ sessionPrompt
61
+ });
50
62
  const metadataTitle = nativeCodexRename ? null : input.customTitle;
51
63
  const updated = this.options.runtime.updateSessionMetadata({
52
64
  sessionId: current.id,
65
+ ...(sessionPrompt === undefined ? {} : { sessionPrompt }),
53
66
  ...(metadataTitle === undefined ? {} : { customTitle: metadataTitle }),
54
67
  ...(input.pinned === undefined ? {} : { pinned: input.pinned })
55
68
  });
56
- return {
57
- session: this.options.present(nativeCodexRename
58
- ? this.options.runtime.setRunnerTitleFromNative(updated.id, input.customTitle ?? '')
59
- : updated)
60
- };
69
+ const next = nativeCodexRename
70
+ ? this.options.runtime.setRunnerTitleFromNative(updated.id, input.customTitle ?? '')
71
+ : updated;
72
+ if (sessionPrompt !== undefined)
73
+ this.options.emitSession(next);
74
+ return { session: this.options.present(next) };
61
75
  }
62
76
  movePin(input) {
63
77
  if (typeof input.sessionId !== 'string' ||
@@ -9,7 +9,12 @@ export class SessionPresentationService {
9
9
  if (session.externalSessionId !== null && database !== undefined) {
10
10
  const metadata = database.getNativeSessionMetadata(session.runner, session.externalSessionId, session.workspaceId);
11
11
  if (metadata !== undefined)
12
- session = { ...session, pinnedAt: metadata.pinnedAt, pinOrder: metadata.pinOrder };
12
+ session = {
13
+ ...session,
14
+ sessionPrompt: metadata.sessionPrompt ?? null,
15
+ pinnedAt: metadata.pinnedAt,
16
+ pinOrder: metadata.pinOrder
17
+ };
13
18
  }
14
19
  const presentation = this.options.runners
15
20
  .require(session.runner)
@@ -23,7 +28,7 @@ export class SessionPresentationService {
23
28
  runtime: {
24
29
  ...presentation.runtime,
25
30
  capabilities: session.externalSessionId !== null && database !== undefined
26
- ? [...presentation.runtime.capabilities, 'session.schedule']
31
+ ? [...presentation.runtime.capabilities, 'session.schedule', 'session.prompt']
27
32
  : presentation.runtime.capabilities
28
33
  },
29
34
  schedule: database === undefined
@@ -1,4 +1,15 @@
1
1
  const MAX_CONTENT_LENGTH = 20_000;
2
+ export function normalizeSessionPrompt(value) {
3
+ if (value === null)
4
+ return null;
5
+ if (typeof value !== 'string' || value.trim().length > MAX_CONTENT_LENGTH)
6
+ throw new Error('SESSION_PROMPT_INVALID');
7
+ return value.trim() || null;
8
+ }
9
+ export function withSessionPrompt(personalInstructions, sessionPrompt) {
10
+ const merged = [personalInstructions, sessionPrompt].filter(Boolean).join('\n\n');
11
+ return merged.length === 0 ? undefined : merged;
12
+ }
2
13
  export function personalInstructionsForRunner(value, runner) {
3
14
  if (value === undefined)
4
15
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
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.32",
41
- "@myagentroam/code-navigation": "0.10.1",
42
- "@myagentroam/marctl": "0.10.4",
43
- "@myagentroam/protocol": "0.10.32"
40
+ "@myagentroam/agent": "0.10.33",
41
+ "@myagentroam/protocol": "0.10.33",
42
+ "@myagentroam/marctl": "0.10.5",
43
+ "@myagentroam/code-navigation": "0.10.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"