@myagentroam/node 0.12.1 → 0.12.3

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.
@@ -450,7 +450,7 @@ export class HostedConversationNodeService {
450
450
  async startMessage(payload, input, steerPrompt) {
451
451
  if (this.disablePrepared)
452
452
  throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
453
- let conversation = this.options.store.requireConversation(payload.userId, input.conversationId);
453
+ const conversation = this.options.store.requireConversation(payload.userId, input.conversationId);
454
454
  this.assertTemplateContext(payload, conversation);
455
455
  if (this.cleanupLocks.has(conversation.id))
456
456
  throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
@@ -1 +1 @@
1
- {"type":"module","version":"0.12.1"}
1
+ {"type":"module","version":"0.12.3"}
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { DirectFallbackError, DirectUnsafeError, directControlError } from './direct-errors.mjs';
3
- import { cancelSession, withTimeout, rawToString, isRecord } from './direct-control.mjs';
2
+ import { DirectFallbackError, directControlError } from './direct-errors.mjs';
3
+ import { rawToString, isRecord } from './direct-control.mjs';
4
4
 
5
5
  export class DirectTerminalTransport extends EventEmitter {
6
6
  #channel;
@@ -310,8 +310,8 @@ export async function openDirectTerminal(options) {
310
310
  signal
311
311
  );
312
312
  return transport;
313
- } catch (error) {
314
- error = preparationError ?? error;
313
+ } catch (caughtError) {
314
+ const error = preparationError ?? caughtError;
315
315
  transportControlCleanup?.();
316
316
  transportControlCleanup = undefined;
317
317
  unsafeFallback = transport?.openSent === true;
@@ -28,7 +28,7 @@ export const marctlInstructions = [
28
28
  'Prefer agents send <id> --steer -m <text> for timely communication or course correction. --queue is best reserved for deferred input explicitly requested by the user or recipient: it is consumed after current work and earlier queued inputs finish, and with an active Goal it is not consumed until that Goal exits. Acceptance is not a guarantee of immediate processing.',
29
29
  'An accepted message normally needs no separate acknowledgment. Requests for receipt-only replies and immediate agents view checks solely to confirm receipt are usually unnecessary.',
30
30
  'Send can update a Goal alone with --goal <objective> or --clear-goal; omitted Goal leaves it unchanged. Decide an authorized Agent approval with send <id> --approval <approval-id> --decision approve|reject.',
31
- 'List accepts only --scope tree|active, optional --query <title-fragment> and --limit <1..100> (default 10). With a current Session it defaults to the containing collaboration tree, including idle Sessions; without one it defaults to active Workspace Sessions (running or sleeping), and explicit tree is rejected. Active Sessions come first, then recent activity descending. Returns currentAgentId (null without a Session), flat sessions with id, parentSessionId, title and status, and truncated; no cursor or pagination. Reuse known IDs directly.',
31
+ 'List accepts only --scope tree|active, optional --query <title-fragment> and --limit <1..100> (default 10). With a current Session it defaults to the containing collaboration tree, including idle Sessions; without one it defaults to active Workspace Sessions (running or sleeping), and explicit tree is rejected. Active Sessions come first, then recent activity descending. Returns scope, currentAgentId (null without a Session), flat sessions with id, parentSessionId, title and status, and truncated; no cursor or pagination. Reuse known IDs directly.',
32
32
  'Only direct-child updates are collected automatically. Sending to a non-child never subscribes; an explicit wait observes it only for that call.',
33
33
  'Use agents config, rename, prompt and schedule for management. marctl nodes, ssh and scp use authorized Nodes.',
34
34
  marctlSafety
@@ -365,7 +365,7 @@ function deleteTranscriptIndex(key) {
365
365
  export async function locateNativeSessionPath(runner, workspacePath, externalSessionId) {
366
366
  const wanted = canonical(workspacePath);
367
367
  const key = indexKey(runner, wanted, externalSessionId);
368
- let path = getTranscriptIndex(key);
368
+ const path = getTranscriptIndex(key);
369
369
  if (path !== undefined)
370
370
  return path;
371
371
  const candidates = await nativeTranscriptAdapter(runner)?.locateCandidates?.(externalSessionId);
@@ -394,7 +394,7 @@ export class CodexRunner extends AbstractRunner {
394
394
  const threads = [];
395
395
  const cursors = new Set();
396
396
  let cursor;
397
- do {
397
+ for (;;) {
398
398
  const result = await this.client.listThreads({
399
399
  limit: 200,
400
400
  ...(cursor === undefined ? {} : { cursor })
@@ -415,7 +415,7 @@ export class CodexRunner extends AbstractRunner {
415
415
  throw new Error('CODEX_THREAD_LIST_INVALID');
416
416
  cursors.add(next);
417
417
  cursor = next;
418
- } while (true);
418
+ }
419
419
  }
420
420
  async discoverSessions(context, workspace) {
421
421
  const [threads, nativeSessions] = context.cleanup
@@ -1,36 +1,28 @@
1
1
  export function codexImagegenSkill() {
2
2
  return `## Working with images
3
3
 
4
- Use codex_image_gen only when the user requests image generation or editing, or explicitly authorizes that work as part of the task. An opportunity to add a visual is not authorization. Loading this skill does not itself generate anything.
4
+ Use codex_image_gen only for image generation or editing the user requests or explicitly authorizes. Loading this skill is not authorization to add visuals.
5
5
 
6
6
  ### Choose the operation
7
- - Use AI image generation for requested raster artwork, photographs, textures, or visual variants. Prefer existing editable vector/code-native sources for deterministic changes to those assets; do not replace them with generated bitmaps merely because this tool exists.
8
- - Distinguish a new composition, an edit target, and a reference for style or subject. Label each supplied image by index and role in the prompt. A reference-guided new composition still needs the reference pixels: pass an image selector rather than silently omitting them.
9
- - Inspect an unseen local input with view_image when that tool is available. With a TEXT-only model, use the user-supplied reference path and instructions without claiming to have inspected pixels. Prefer referenced_image_paths when all necessary images have valid absolute paths. The Host's file boundaries still apply.
10
- - Use num_last_images_to_include only when a necessary image has no usable path, and choose the smallest suffix containing all inputs. This is the current Execution's available in-memory images, not a permanent conversation gallery. After a new Execution or Compact, use valid saved paths or ask for missing inputs again.
11
- - The selectors are mutually exclusive and include at most five images. Omit both only for generation without input images. Do not pretend an unavailable reference was used.
7
+ - Use generated raster artwork for photos, illustrations, textures, and visual variants. Prefer direct source edits for existing SVG, icons, and other code-native assets.
8
+ - Omit both selectors for generation without references. Otherwise use up to five absolute referenced_image_paths, or the smallest num_last_images_to_include containing all needed images, never both. Prefer paths when all inputs have them. Recent images belong to this Execution, not a permanent gallery; after recovery or Compact, use saved paths or ask for missing inputs.
9
+ - Identify each reference's role. Inspect unseen PNG/JPEG/WebP inputs with view_image when available; TEXT-only models must not claim visual inspection. Do not silently omit required references.
12
10
 
13
11
  ### Shape the prompt
14
- - Preserve the user's specifics and exact requested text. Organize them into subject, intended use, composition, style, input-image roles, and constraints as useful; a fixed template is not required.
15
- - For a vague request, supply only details that support its stated goal. Do not invent brands, slogans, additional subjects, or unrelated requirements.
16
- - For edits, identify the target and state what changes and what must remain unchanged, including identity, layout, text, or other important details. Carry these invariants into follow-up edits.
17
- - For lettering, quote the literal text and specify placement and typography when relevant. For multiple images, explain which image supplies each element.
18
- - Ask for a missing decision only if it materially blocks the requested result; do not request a second approval for an already authorized operation.
12
+ - State the subject, composition, style, and essential constraints; preserve exact requested lettering. For edits, specify what changes and what stays unchanged. Add only details that support the user's goal; clarify only when a missing decision blocks the result.
13
+ - Set transparent_background: true for cutouts, background removal, or transparency; preserve existing transparency during edits unless the user asks to change it. Preserve real alpha, not a drawn checkerboard.
14
+ - For a simple looping animation, set gif: {} or specify frame_count (2–16) and frame_duration_ms (20–2000). Defaults are 9 frames and 200 ms. Describe the motion and loop seam; keep subjects and camera stable unless asked otherwise.
15
+ - To edit a GIF, first inspect its frames and use available tools to make a PNG spritesheet with equal-sized cells in a regular grid. Pass that spritesheet through referenced_image_paths and set gif. Do not pass the GIF itself as an edit input; if preparation is unavailable, explain the limitation.
16
+ - For complex animation or more than 16 frames, use suitable available tools or explain the limit rather than stretching this tool's bounds.
19
17
 
20
18
  ### Generate and check
21
- - Call codex_image_gen with its actual schema. It produces one PNG per call; use separate calls for separately requested assets. Do not add unsupported parameters for model, dimensions, quality, masks, output format, or destination.
22
- - In Code Mode, call tools.codex_image_gen. Independent calls can run with Promise.all; await earlier results when later images depend on them.
23
- - The service configuration controls model and automatic size/quality/background. A prompt can request an aspect ratio or transparency, but these are not guaranteed API settings. Check the actual result before claiming compliance.
24
- - Generation may take minutes. Await the tool result; do not blindly repeat a timed-out or interrupted request because the upstream operation may already have run. Report failures rather than claiming an artifact exists.
25
- - A successful result returns the saved path, not inspected pixels. When view_image is available, use it for visual acceptance: subject, exact text, composition, edit invariants, and unwanted elements. Otherwise explicitly state that visual acceptance was not performed. Make targeted corrections only within the requested scope.
26
- - For transparent deliverables, verify actual transparency rather than treating a drawn checkerboard as alpha. Preserve the alpha channel when copying or processing the result.
19
+ - One PNG or GIF is saved per call. Model, quality, and size remain service-controlled; do not invent API parameters, output paths, SVG support, or fallback tools.
20
+ - In Code Mode, call tools.codex_image_gen. Independent calls may use Promise.all; dependent edits must await their inputs. Generation can take minutes: await the result and do not blindly replay an interrupted or timed-out request. Report failures rather than claiming an artifact exists.
21
+ - Verify the actual format and requested transparency, then check PNG content (subject, lettering, and edit invariants) with view_image when available. For GIFs, check frames, timing, and the loop with available animation-aware tools; a first-frame preview does not verify animation. If inspection is unavailable, say so. Correct only issues within the requested scope.
27
22
 
28
23
  ### Keep and deliver
29
- - Treat the tool-returned path as authoritative; do not guess a product HOME or generated-image directory.
30
- - Keep the managed original. Copy selected assets into the user-requested, Host-permitted destination when needed by the project. Do not leave a project dependency pointing only at private Session storage.
31
- - Avoid overwriting existing assets unless replacement was requested. For preview-only work the managed file can remain in place.
32
- - If the user asks to reuse an existing image, inspect or copy it rather than generating a replacement. If it is missing, state that fact; generate again only when authorized.
33
- - Report the actual final path and any material unmet constraints. Do not dump a long generation prompt unless the user requests it.
24
+ - Trust the returned path and keep the managed original. Copy assets into the authorized project destination when needed; do not leave project dependencies in private Session storage. Do not overwrite existing assets unless replacement was requested.
25
+ - Reuse existing images when requested rather than regenerating them. Report the final path and material unmet constraints, not a long prompt dump.
34
26
 
35
- This Host-provided skill has no bundled command-line fallback, install step, or separate API-key requirement. Do not invent scripts or switch credentials, endpoints, or models. If the tool or a required capability is unavailable, explain the limitation. Workspace instructions can supply additional authorized workflows without changing the tool schema or Host boundaries.`;
27
+ This Host-provided skill has no bundled CLI fallback or separate API-key setup. Do not switch credentials, endpoints, or models. Follow the tool schema and Host file boundaries.`;
36
28
  }
@@ -174,14 +174,27 @@ export class MarAgentConversationNormalizer {
174
174
  const state = this.#tools.get(event.callId);
175
175
  if (!state?.active ||
176
176
  state.toolName !== event.toolName ||
177
- !agentOperationAction(event.toolName))
177
+ state.executionId !== event.executionId ||
178
+ (state.turnId !== undefined && event.turnId !== undefined && state.turnId !== event.turnId))
178
179
  return [];
179
- const operation = agentOperationPresentationSchema.safeParse(record(event.data)?.agentOperation);
180
- if (!operation.success || operation.data.action !== agentOperationAction(event.toolName))
181
- return [];
182
- this.#tools.set(event.callId, { ...state, presentationData: event.data });
180
+ let data;
181
+ if (event.toolName === 'exec_wait' ||
182
+ event.toolName === 'code_exec_wait' ||
183
+ (event.toolName === 'exec' && state.commandOrigin === undefined)) {
184
+ const source = executionSource(record(event.data)?.executionSource).source;
185
+ if (!source)
186
+ return [];
187
+ data = { executionSource: source };
188
+ }
189
+ else {
190
+ const operation = agentOperationPresentationSchema.safeParse(record(event.data)?.agentOperation);
191
+ if (!operation.success || operation.data.action !== agentOperationAction(event.toolName))
192
+ return [];
193
+ data = event.data;
194
+ }
195
+ this.#tools.set(event.callId, { ...state, presentationData: data });
183
196
  return [
184
- genericToolItem(state.itemId, state.toolName, 'IN_PROGRESS', state.input, null, null, false, event.data, state.subagentTitle, state.subagentDetails, undefined, undefined, state.inputTruncated)
197
+ genericToolItem(state.itemId, state.toolName, 'IN_PROGRESS', state.input, null, null, false, data, state.subagentTitle, state.subagentDetails, undefined, undefined, state.inputTruncated)
185
198
  ];
186
199
  }
187
200
  if (event.type === 'sleep.started') {
@@ -323,6 +336,7 @@ export class MarAgentConversationNormalizer {
323
336
  return [];
324
337
  return [
325
338
  genericToolItem(state.itemId, toolName, 'IN_PROGRESS', state.input, snapshot.outputPreview, null, snapshot.truncated, {
339
+ ...record(state.presentationData),
326
340
  processId: output.processId,
327
341
  displayOutput: snapshot.displayOutput,
328
342
  outputHeadTruncated: snapshot.displayOutput.stdoutDroppedBytes > 0 ||
@@ -397,7 +411,7 @@ export class MarAgentConversationNormalizer {
397
411
  const command = target.commandOrigin === undefined ? undefined : this.#commands.get(target.commandOrigin);
398
412
  const item = command
399
413
  ? commandItem(command, 'IN_PROGRESS')
400
- : genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, null, null, false, undefined, undefined, undefined, undefined, undefined, target.inputTruncated);
414
+ : genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, null, null, false, target.presentationData, undefined, undefined, undefined, undefined, target.inputTruncated);
401
415
  const blocks = [
402
416
  ...(output.stdout ? [{ channel: 'STDOUT', text: output.stdout }] : []),
403
417
  ...(output.stderr ? [{ channel: 'STDERR', text: output.stderr }] : [])
@@ -451,6 +465,7 @@ export class MarAgentConversationNormalizer {
451
465
  const bounded = publicOutputSnapshot(combined, entries.some((entry) => entry.truncated));
452
466
  return [
453
467
  genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, bounded.text, null, bounded.truncated, {
468
+ ...record(target.presentationData),
454
469
  outputHeadTruncated: entries.some((entry) => entry.displayOutput.stdoutDroppedBytes > 0 ||
455
470
  entry.displayOutput.stderrDroppedBytes > 0),
456
471
  displayOutput: {
@@ -602,7 +617,7 @@ export class MarAgentConversationNormalizer {
602
617
  this.#clearCodeModeOutputSnapshots(sourceCallId);
603
618
  }
604
619
  #startExecution(executionId) {
605
- for (const [callId, state] of this.#tools)
620
+ for (const state of this.#tools.values())
606
621
  if (state.executionId !== executionId) {
607
622
  state.active = false;
608
623
  }
@@ -687,7 +702,7 @@ export class MarAgentConversationNormalizer {
687
702
  }
688
703
  const item = genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, 'COMPLETED', state?.input, generatedImages.length === 0
689
704
  ? event.summary
690
- : generatedImages.map((image) => image.name).join(', '), null, event.outputTruncation !== undefined, mergeAgentOperationData(event.toolName, state?.presentationData, event.data), agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated, event.summaryTruncated === true);
705
+ : generatedImages.map((image) => image.name).join(', '), null, event.outputTruncation !== undefined, mergeToolPresentationData(event.toolName, state?.presentationData, event.data), agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated, event.summaryTruncated === true);
691
706
  return [
692
707
  {
693
708
  ...item,
@@ -718,11 +733,7 @@ export class MarAgentConversationNormalizer {
718
733
  return [
719
734
  genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, event.code === 'MAR_AGENT_CANCELLED' || event.code === 'MAR_AGENT_EXECUTION_CANCELLED'
720
735
  ? 'CANCELLED'
721
- : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, agentOperationAction(event.toolName)
722
- ? mergeAgentOperationData(event.toolName, state?.presentationData, event.data)
723
- : event.data === undefined
724
- ? state?.presentationData
725
- : event.data, state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated)
736
+ : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, mergeToolPresentationData(event.toolName, state?.presentationData, event.data === undefined ? state?.presentationData : event.data), state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated)
726
737
  ];
727
738
  }
728
739
  #codeModeToolResults(event) {
@@ -1045,7 +1056,12 @@ function codeExecutionError(data) {
1045
1056
  message: compactRunnerText(error.message, 1000)
1046
1057
  };
1047
1058
  }
1048
- function mergeAgentOperationData(toolName, previous, current) {
1059
+ function mergeToolPresentationData(toolName, previous, current) {
1060
+ if (toolName === 'exec_wait' || toolName === 'code_exec_wait' || toolName === 'exec') {
1061
+ const source = executionSource(record(current)?.executionSource).source ??
1062
+ executionSource(record(previous)?.executionSource).source;
1063
+ return source ? { ...record(current), executionSource: source } : current;
1064
+ }
1049
1065
  if (!agentOperationAction(toolName))
1050
1066
  return current;
1051
1067
  const before = agentOperationPresentationSchema.safeParse(record(previous)?.agentOperation);
@@ -1734,20 +1750,6 @@ function truncateCharacters(value, maximumCharacters) {
1734
1750
  ? value
1735
1751
  : characters.slice(0, maximumCharacters).join('');
1736
1752
  }
1737
- function boundedToolSummary(value) {
1738
- const maximumCodeUnits = 10_000;
1739
- if (value.length <= maximumCodeUnits)
1740
- return value;
1741
- let end = maximumCodeUnits - 1;
1742
- const lastIncluded = value.charCodeAt(end - 1);
1743
- const firstExcluded = value.charCodeAt(end);
1744
- if (lastIncluded >= 0xd800 &&
1745
- lastIncluded <= 0xdbff &&
1746
- firstExcluded >= 0xdc00 &&
1747
- firstExcluded <= 0xdfff)
1748
- end--;
1749
- return `${value.slice(0, end)}…`;
1750
- }
1751
1753
  function marAgentAgentId(value) {
1752
1754
  const data = structuredRecord(value);
1753
1755
  if (data === undefined)
@@ -1798,7 +1800,7 @@ function imageArtifacts(event) {
1798
1800
  const name = typeof artifact?.name === 'string' ? compactRunnerText(artifact.name, 255) : '';
1799
1801
  const byteSize = nonnegativeInteger(artifact?.byteSize);
1800
1802
  if (artifact?.kind !== 'image' ||
1801
- artifact.mimeType !== 'image/png' ||
1803
+ (artifact.mimeType !== 'image/png' && artifact.mimeType !== 'image/gif') ||
1802
1804
  typeof artifact.sha256 !== 'string' ||
1803
1805
  !/^[a-f0-9]{64}$/iu.test(artifact.sha256) ||
1804
1806
  name.length === 0 ||
@@ -1810,8 +1812,8 @@ function imageArtifacts(event) {
1810
1812
  fileName === '..' ||
1811
1813
  fileName.includes('/') ||
1812
1814
  fileName.includes('\\') ||
1813
- !fileName.toLocaleLowerCase().endsWith('.png'))
1815
+ !fileName.endsWith(artifact.mimeType === 'image/gif' ? '.gif' : '.png'))
1814
1816
  return [];
1815
- return [{ name, mime: 'image/png', byteSize, fileName }];
1817
+ return [{ name, mime: artifact.mimeType, byteSize, fileName }];
1816
1818
  });
1817
1819
  }
@@ -19,7 +19,7 @@ export function createImageGenerationExtensions(input) {
19
19
  {
20
20
  name: codex ? 'codex-imagegen' : 'byted_ark-imagegen',
21
21
  description: codex
22
- ? 'Generate or edit raster images with codex_image_gen only when explicitly requested. Load before calling the image tool.'
22
+ ? 'Generate or edit PNG images and looping GIFs with codex_image_gen only when explicitly requested. Load before calling the image tool.'
23
23
  : 'Generate or edit one image with byted_ark_image_gen only when explicitly requested. Load before calling the image tool.',
24
24
  requiredTools: [tool.name],
25
25
  load: async () => codex
@@ -26,7 +26,7 @@ export const codexImageGenToolDefinition = {
26
26
  agentScope: 'ROOT_ONLY',
27
27
  allowedCallers: ['direct', 'programmatic'],
28
28
  parallelSafety: 'safe',
29
- description: 'Only when the user requests or explicitly authorizes image generation or editing, use the configured Codex Images API. First load and follow skill({name:"codex-imagegen"}); reload if its guidance is no longer in context. Do not generate unsolicited visuals. Omit both selectors for a new image without references. For image inputs, inspect unseen local images with view_image when available; TEXT-only models must not claim visual inspection. Use referenced_image_paths when every image has an absolute path; otherwise use num_last_images_to_include for the smallest sufficient number of currently available conversation images. Never provide both selectors. Include at most five images; if required inputs are missing, ask for them rather than silently omitting them. Recent images are in-memory in the current Execution, not a permanent history library. One PNG is saved in the current Session and its path is returned, not inspected pixels. Use view_image when available to check the result; otherwise report that visual acceptance was not performed. Full workflow guidance is in the codex-imagegen skill.',
29
+ description: 'Generate or edit one PNG or simple looping GIF with the configured Codex Images API, only when requested or explicitly authorized. First load skill({name:"codex-imagegen"}); reuse it while in context. Omit selectors only for generation without references; otherwise use up to five absolute referenced_image_paths OR the smallest sufficient num_last_images_to_include from this Execution. Set transparent_background for transparency and gif for 2–16 animation frames. Returns a managed file path, not inspected pixels.',
30
30
  inputSchema: {
31
31
  type: 'object',
32
32
  properties: {
@@ -44,6 +44,19 @@ export const codexImageGenToolDefinition = {
44
44
  minimum: 1,
45
45
  maximum: MAX_EDIT_IMAGES,
46
46
  description: 'Number of most recent in-memory conversation images to edit.'
47
+ },
48
+ transparent_background: {
49
+ type: 'boolean',
50
+ description: 'Request a transparent background; defaults to false.'
51
+ },
52
+ gif: {
53
+ type: 'object',
54
+ description: 'Request a simple looping GIF. Use {} for 9 frames at 200 ms per frame.',
55
+ properties: {
56
+ frame_count: { type: 'integer', minimum: 2, maximum: 16 },
57
+ frame_duration_ms: { type: 'integer', minimum: 20, maximum: 2000 }
58
+ },
59
+ additionalProperties: false
47
60
  }
48
61
  },
49
62
  required: ['prompt'],
@@ -53,6 +66,10 @@ export const codexImageGenToolDefinition = {
53
66
  export const bytedArkImageGenToolDefinition = {
54
67
  ...codexImageGenToolDefinition,
55
68
  name: 'byted_ark_image_gen',
69
+ inputSchema: {
70
+ ...codexImageGenToolDefinition.inputSchema,
71
+ properties: Object.fromEntries(Object.entries(codexImageGenToolDefinition.inputSchema.properties ?? {}).filter(([key]) => key !== 'transparent_background' && key !== 'gif'))
72
+ },
56
73
  description: 'Generate or edit one PNG using the configured Ark Images API, only when explicitly requested. First load skill({name:"byted_ark-imagegen"}). Omit both selectors for text-to-image; use absolute referenced_image_paths or num_last_images_to_include for image-to-image, never both. Up to five references, exactly one output; no group generation. Returns a managed image path, not inspected pixels.'
57
74
  };
58
75
  export async function executeCodexImageGen(arguments_, input) {
@@ -60,11 +77,11 @@ export async function executeCodexImageGen(arguments_, input) {
60
77
  const capability = input.configuration.imageGeneration;
61
78
  if (capability === undefined || capability.credentialProvider === undefined)
62
79
  throw new AgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Image generation requires an independent credential provider.');
63
- const request = parseInput(arguments_);
64
- const images = await selectedEditImages(request, input);
65
80
  const ark = capability.provider === 'BYTED_ARK';
66
81
  if (!ark && capability.provider !== 'CODEX')
67
82
  throw new AgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Unsupported image provider.');
83
+ const request = parseInput(arguments_, ark);
84
+ const images = await selectedEditImages(request, input);
68
85
  const endpoint = images.length === 0 ? capability.generateEndpoint : capability.editEndpoint;
69
86
  const body = JSON.stringify(ark
70
87
  ? {
@@ -93,11 +110,11 @@ export async function executeCodexImageGen(arguments_, input) {
93
110
  }))
94
111
  }),
95
112
  prompt: request.prompt,
96
- background: 'auto',
113
+ background: request.transparentBackground ? 'transparent' : 'opaque',
97
114
  model: capability.imageModelId,
98
- n: 1,
99
115
  quality: 'auto',
100
- size: 'auto'
116
+ size: 'auto',
117
+ ...(request.gif === undefined ? {} : { output_format: 'gif', ...request.gif })
101
118
  });
102
119
  const turnId = randomUUID();
103
120
  let reason = 'REQUEST';
@@ -136,8 +153,10 @@ export async function executeCodexImageGen(arguments_, input) {
136
153
  if (!response.ok)
137
154
  throw imageGenerationResponseError(response.status, payload);
138
155
  const encoded = imageBase64(payload);
139
- const data = decodePng(encoded);
140
- const fileName = generatedFileName(input.callId);
156
+ const { data, format } = decodeImage(encoded);
157
+ if (ark && format !== 'png')
158
+ throw new AgentError('MAR_AGENT_IMAGE_GENERATION_RESPONSE_INVALID', 'Ark image response must be a PNG.');
159
+ const fileName = generatedFileName(input.callId, format);
141
160
  const directory = join(input.sessionDirectory, 'generated_images');
142
161
  const path = join(directory, fileName);
143
162
  const displayPath = input.displayDirectory === undefined ? path : posix.join(input.displayDirectory, fileName);
@@ -160,7 +179,7 @@ export async function executeCodexImageGen(arguments_, input) {
160
179
  {
161
180
  kind: 'image',
162
181
  name: fileName,
163
- mimeType: 'image/png',
182
+ mimeType: format === 'gif' ? 'image/gif' : 'image/png',
164
183
  byteSize: data.byteLength,
165
184
  sha256,
166
185
  fileName
@@ -170,16 +189,21 @@ export async function executeCodexImageGen(arguments_, input) {
170
189
  }
171
190
  throw new AgentError('MAR_AGENT_IMAGE_GENERATION_FAILED', 'Image generation failed.');
172
191
  }
173
- function parseInput(value) {
192
+ function parseInput(value, ark) {
174
193
  if (value === null || typeof value !== 'object' || Array.isArray(value))
175
194
  return invalidInput();
176
195
  const input = value;
177
196
  const referencedImagePaths = input.referenced_image_paths === null ? undefined : input.referenced_image_paths;
178
197
  const numLastImagesToInclude = input.num_last_images_to_include === null ? undefined : input.num_last_images_to_include;
179
- if (Object.keys(input).some((key) => key !== 'prompt' && key !== 'referenced_image_paths' && key !== 'num_last_images_to_include') ||
198
+ const transparentBackground = input.transparent_background ?? false;
199
+ if (Object.keys(input).some((key) => key !== 'prompt' &&
200
+ key !== 'referenced_image_paths' &&
201
+ key !== 'num_last_images_to_include' &&
202
+ (ark || (key !== 'gif' && key !== 'transparent_background'))) ||
180
203
  typeof input.prompt !== 'string' ||
181
204
  input.prompt.trim().length === 0 ||
182
205
  input.prompt.length > 32_768 ||
206
+ typeof transparentBackground !== 'boolean' ||
183
207
  (referencedImagePaths !== undefined &&
184
208
  (!Array.isArray(referencedImagePaths) ||
185
209
  referencedImagePaths.length > MAX_EDIT_IMAGES ||
@@ -194,12 +218,32 @@ function parseInput(value) {
194
218
  return invalidInput();
195
219
  return {
196
220
  prompt: input.prompt,
221
+ transparentBackground,
222
+ ...(input.gif === undefined || input.gif === null ? {} : { gif: parseGif(input.gif) }),
197
223
  ...(paths?.length ? { referencedImagePaths: [...paths] } : {}),
198
224
  ...(numLastImagesToInclude === undefined
199
225
  ? {}
200
226
  : { numLastImagesToInclude: numLastImagesToInclude })
201
227
  };
202
228
  }
229
+ function parseGif(value) {
230
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
231
+ return invalidInput();
232
+ const options = value;
233
+ if (Object.keys(options).some((key) => key !== 'frame_count' && key !== 'frame_duration_ms'))
234
+ return invalidInput();
235
+ const count = options.frame_count ?? undefined;
236
+ const duration = options.frame_duration_ms ?? undefined;
237
+ if ((count !== undefined &&
238
+ (!Number.isInteger(count) || count < 2 || count > 16)) ||
239
+ (duration !== undefined &&
240
+ (!Number.isInteger(duration) || duration < 20 || duration > 2000)))
241
+ return invalidInput();
242
+ return {
243
+ ...(count === undefined ? {} : { frame_count: count }),
244
+ ...(duration === undefined ? {} : { frame_duration_ms: duration })
245
+ };
246
+ }
203
247
  async function selectedEditImages(request, input) {
204
248
  if (request.referencedImagePaths !== undefined) {
205
249
  const images = [];
@@ -310,31 +354,36 @@ function imageBase64(value) {
310
354
  throw new AgentError('MAR_AGENT_IMAGE_GENERATION_RESPONSE_INVALID', 'Image response is invalid.');
311
355
  return first;
312
356
  }
313
- function decodePng(encoded) {
357
+ function decodeImage(encoded) {
314
358
  if (encoded.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/u.test(encoded))
315
359
  throw new AgentError('MAR_AGENT_IMAGE_GENERATION_RESPONSE_INVALID', 'Image response is invalid.');
316
360
  const data = Buffer.from(encoded, 'base64');
361
+ const signature = data.subarray(0, 6);
362
+ const format = signature.equals(Buffer.from('GIF87a')) || signature.equals(Buffer.from('GIF89a'))
363
+ ? 'gif'
364
+ : 'png';
317
365
  if (data.byteLength === 0 ||
318
366
  data.byteLength > IMAGE_MAX_BYTES ||
319
367
  data.toString('base64') !== encoded ||
320
- !data.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE))
321
- throw new AgentError('MAR_AGENT_IMAGE_GENERATION_RESPONSE_INVALID', 'Image response is not a supported PNG.');
322
- return data;
368
+ (format === 'png' && !data.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)))
369
+ throw new AgentError('MAR_AGENT_IMAGE_GENERATION_RESPONSE_INVALID', 'Image response is not a supported PNG or GIF.');
370
+ return { data, format };
323
371
  }
324
- function generatedFileName(callId) {
372
+ function generatedFileName(callId, format) {
325
373
  const normalized = callId
326
374
  .normalize('NFKC')
327
375
  .replace(/[^a-zA-Z0-9._-]+/gu, '_')
328
376
  .replace(/^[_\-.]+|[_\-.]+$/gu, '')
329
377
  .slice(0, 96);
330
378
  const stem = normalized.length > 0 ? normalized : createHash('sha256').update(callId).digest('hex');
331
- const fileName = `${stem.startsWith('image_') ? stem : `image_${stem}`}.png`;
379
+ const fileName = `${stem.startsWith('image_') ? stem : `image_${stem}`}.${format}`;
332
380
  return basename(fileName);
333
381
  }
334
382
  function imageGenerationResponseError(status, value) {
335
- const error = value !== null && typeof value === 'object' && !Array.isArray(value)
336
- ? value.error
383
+ const payload = value !== null && typeof value === 'object' && !Array.isArray(value)
384
+ ? value
337
385
  : undefined;
386
+ const error = payload?.error;
338
387
  const record = error !== null && typeof error === 'object' && !Array.isArray(error)
339
388
  ? error
340
389
  : undefined;
@@ -343,7 +392,11 @@ function imageGenerationResponseError(status, value) {
343
392
  const code = /^[a-zA-Z0-9._-]{1,80}$/u.test(rawCode)
344
393
  ? rawCode
345
394
  : 'MAR_AGENT_IMAGE_GENERATION_FAILED';
346
- const rawMessage = typeof record?.message === 'string' ? record.message.trim() : '';
395
+ const rawMessage = typeof record?.message === 'string'
396
+ ? record.message.trim()
397
+ : typeof payload?.detail === 'string'
398
+ ? payload.detail.trim()
399
+ : '';
347
400
  const message = rawMessage.length > 0 &&
348
401
  rawMessage.length <= 1_000 &&
349
402
  !/\b(?:authorization|bearer|api[_ -]?key|access[_ -]?token|refresh[_ -]?token|password|secret|proxy-authorization|chatgpt-account-id)\b|socks5:\/\//iu.test(rawMessage)
@@ -78,11 +78,13 @@ export class MarAgentSessionRuntimeRegistry {
78
78
  this.#assertUsable(entry, lifetime);
79
79
  }
80
80
  const request = nativeId === undefined ? input : { ...input, externalSessionId: nativeId };
81
- let created;
82
- created = await createSessionRuntime(request, nativeId ?? input.sessionId, fingerprint, configuration, this.resources, () => created !== undefined && entry.runtime === created && entry.release === undefined
81
+ const created = {};
82
+ created.runtime = await createSessionRuntime(request, nativeId ?? input.sessionId, fingerprint, configuration, this.resources, () => created.runtime !== undefined &&
83
+ entry.runtime === created.runtime &&
84
+ entry.release === undefined
83
85
  ? entry.current?.runId
84
86
  : undefined);
85
- uncommitted = created;
87
+ uncommitted = created.runtime;
86
88
  this.#addAlias(entry, uncommitted.session.id);
87
89
  nodeLog('runner.mar-agent.runtime.created', {
88
90
  runtimeId: uncommitted.runtimeId,
@@ -65,7 +65,7 @@ async function gitExcludeTarget(workspace, includeParentRepository) {
65
65
  .relative(root, await realpath(workspace))
66
66
  .split(path.sep)
67
67
  .join('/');
68
- const escaped = relative.replace(/[\\*?\[\]#! ]/g, '\\$&');
68
+ const escaped = relative.replace(/[\\*?[\]#! ]/g, '\\$&');
69
69
  return { exclude: await resolveGitExclude(root), prefix: relative === '' ? '/' : `/${escaped}/` };
70
70
  }
71
71
  async function mutateGitExclude(workspace, mutate, includeParentRepository) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -42,14 +42,14 @@
42
42
  "turndown-plugin-gfm": "^1.0.2",
43
43
  "ws": "^8.21.3",
44
44
  "zod": "4.4.3",
45
- "@myagentroam/protocol": "0.12.1",
46
- "@myagentroam/agent": "0.12.1"
45
+ "@myagentroam/agent": "0.12.3",
46
+ "@myagentroam/protocol": "0.12.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/turndown": "^5.0.6",
50
50
  "@types/ws": "^8.18.1",
51
51
  "@myagentroam/code-navigation": "0.11.1",
52
- "@myagentroam/marctl": "0.12.1"
52
+ "@myagentroam/marctl": "0.12.3"
53
53
  },
54
54
  "exports": {
55
55
  "./config": {