@myagentroam/node 0.1.3 → 0.1.6

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.
Files changed (195) hide show
  1. package/README.md +9 -1
  2. package/dist/capabilities.d.ts +2 -0
  3. package/dist/capabilities.js +15 -4
  4. package/dist/claude-agent-sdk.d.ts +4 -16
  5. package/dist/claude-agent-sdk.js +18 -79
  6. package/dist/claude-channel.js +0 -1
  7. package/dist/codex-app-server.d.ts +5 -0
  8. package/dist/codex-app-server.js +26 -5
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +18 -2
  11. package/dist/connector/node-connector-options.d.ts +26 -0
  12. package/dist/connector/node-connector-options.js +1 -0
  13. package/dist/connector/node-operation-router.d.ts +9 -0
  14. package/dist/connector/node-operation-router.js +22 -0
  15. package/dist/connector.d.ts +62 -356
  16. package/dist/connector.js +714 -6176
  17. package/dist/control-outbound-scheduler.d.ts +26 -0
  18. package/dist/control-outbound-scheduler.js +121 -0
  19. package/dist/database.d.ts +4 -0
  20. package/dist/database.js +15 -10
  21. package/dist/event-buffer.js +0 -1
  22. package/dist/fake-runner.js +0 -1
  23. package/dist/health.js +0 -1
  24. package/dist/main.js +6 -1
  25. package/dist/migrations/v001.js +0 -1
  26. package/dist/migrations/v002.js +0 -1
  27. package/dist/migrations/v003.d.ts +4 -0
  28. package/dist/migrations/v003.js +44 -0
  29. package/dist/native-jsonl-reader.d.ts +14 -0
  30. package/dist/native-jsonl-reader.js +97 -0
  31. package/dist/native-session-history.d.ts +41 -4
  32. package/dist/native-session-history.js +340 -159
  33. package/dist/opencode-runtime.d.ts +33 -0
  34. package/dist/opencode-runtime.js +80 -0
  35. package/dist/opencode-server.d.ts +47 -0
  36. package/dist/opencode-server.js +250 -0
  37. package/dist/operational.js +10 -3
  38. package/dist/process-tree.js +0 -1
  39. package/dist/runner/abstract-runner.d.ts +153 -0
  40. package/dist/runner/abstract-runner.js +154 -0
  41. package/dist/runner/claude/managed-run-controller.d.ts +52 -0
  42. package/dist/runner/claude/managed-run-controller.js +326 -0
  43. package/dist/runner/claude-code-runner.d.ts +70 -0
  44. package/dist/runner/claude-code-runner.js +286 -0
  45. package/dist/runner/codex/conversation-parser.d.ts +59 -0
  46. package/dist/runner/codex/conversation-parser.js +816 -0
  47. package/dist/runner/codex/managed-run-controller.d.ts +80 -0
  48. package/dist/runner/codex/managed-run-controller.js +388 -0
  49. package/dist/runner/codex-runner.d.ts +85 -0
  50. package/dist/runner/codex-runner.js +365 -0
  51. package/dist/runner/fake-test-runner.d.ts +26 -0
  52. package/dist/runner/fake-test-runner.js +47 -0
  53. package/dist/runner/opencode/conversation-parser.d.ts +27 -0
  54. package/dist/runner/opencode/conversation-parser.js +217 -0
  55. package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
  56. package/dist/runner/opencode/managed-run-controller.js +451 -0
  57. package/dist/runner/opencode-runner.d.ts +60 -0
  58. package/dist/runner/opencode-runner.js +330 -0
  59. package/dist/runner/runner-registry.d.ts +19 -0
  60. package/dist/runner/runner-registry.js +75 -0
  61. package/dist/runner-command-engine.js +0 -1
  62. package/dist/runner-profiles.d.ts +4 -0
  63. package/dist/runner-profiles.js +76 -3
  64. package/dist/runner-usage.d.ts +2 -2
  65. package/dist/runner-usage.js +3 -6
  66. package/dist/runtime-state.js +26 -14
  67. package/dist/service/attachment-domain-state.d.ts +5 -0
  68. package/dist/service/attachment-domain-state.js +5 -0
  69. package/dist/service/conversation-history-service.d.ts +34 -0
  70. package/dist/service/conversation-history-service.js +158 -0
  71. package/dist/service/external-session-resume-service.d.ts +32 -0
  72. package/dist/service/external-session-resume-service.js +98 -0
  73. package/dist/service/fake-managed-run-service.d.ts +29 -0
  74. package/dist/service/fake-managed-run-service.js +62 -0
  75. package/dist/service/managed-runner-session-service.d.ts +12 -0
  76. package/dist/service/managed-runner-session-service.js +35 -0
  77. package/dist/service/native-session-projection-service.d.ts +51 -0
  78. package/dist/service/native-session-projection-service.js +271 -0
  79. package/dist/service/native-session-watch-service.d.ts +37 -0
  80. package/dist/service/native-session-watch-service.js +150 -0
  81. package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
  82. package/dist/service/node-connection-lifecycle-service.js +85 -0
  83. package/dist/service/node-control-channel.d.ts +32 -0
  84. package/dist/service/node-control-channel.js +85 -0
  85. package/dist/service/node-control-message-service.d.ts +28 -0
  86. package/dist/service/node-control-message-service.js +104 -0
  87. package/dist/service/node-request-service.d.ts +14 -0
  88. package/dist/service/node-request-service.js +50 -0
  89. package/dist/service/node-run-event-bridge.d.ts +40 -0
  90. package/dist/service/node-run-event-bridge.js +83 -0
  91. package/dist/service/node-terminal-channel.d.ts +29 -0
  92. package/dist/service/node-terminal-channel.js +182 -0
  93. package/dist/service/node-upgrade-coordinator.d.ts +29 -0
  94. package/dist/service/node-upgrade-coordinator.js +137 -0
  95. package/dist/service/node-workspace-coordinator.d.ts +32 -0
  96. package/dist/service/node-workspace-coordinator.js +80 -0
  97. package/dist/service/run-attachment-service.d.ts +37 -0
  98. package/dist/service/run-attachment-service.js +160 -0
  99. package/dist/service/run-domain-state.d.ts +4 -0
  100. package/dist/service/run-domain-state.js +4 -0
  101. package/dist/service/run-event-service.d.ts +31 -0
  102. package/dist/service/run-event-service.js +102 -0
  103. package/dist/service/run-workbench-service.d.ts +27 -0
  104. package/dist/service/run-workbench-service.js +113 -0
  105. package/dist/service/runner-channel-message-service.d.ts +13 -0
  106. package/dist/service/runner-channel-message-service.js +20 -0
  107. package/dist/service/runner-interaction-service.d.ts +40 -0
  108. package/dist/service/runner-interaction-service.js +246 -0
  109. package/dist/service/runner-service.d.ts +44 -0
  110. package/dist/service/runner-service.js +68 -0
  111. package/dist/service/session-catalog-service.d.ts +45 -0
  112. package/dist/service/session-catalog-service.js +224 -0
  113. package/dist/service/session-command-service.d.ts +23 -0
  114. package/dist/service/session-command-service.js +69 -0
  115. package/dist/service/session-context-service.d.ts +34 -0
  116. package/dist/service/session-context-service.js +87 -0
  117. package/dist/service/session-domain-state.d.ts +8 -0
  118. package/dist/service/session-domain-state.js +8 -0
  119. package/dist/service/session-identity-service.d.ts +24 -0
  120. package/dist/service/session-identity-service.js +86 -0
  121. package/dist/service/session-lifecycle-service.d.ts +31 -0
  122. package/dist/service/session-lifecycle-service.js +242 -0
  123. package/dist/service/session-message-service.d.ts +33 -0
  124. package/dist/service/session-message-service.js +157 -0
  125. package/dist/service/session-presentation-service.d.ts +33 -0
  126. package/dist/service/session-presentation-service.js +36 -0
  127. package/dist/service/session-query-service.d.ts +31 -0
  128. package/dist/service/session-query-service.js +77 -0
  129. package/dist/service/terminal-relay-state.d.ts +6 -0
  130. package/dist/service/terminal-relay-state.js +3 -0
  131. package/dist/service/terminal-service.d.ts +12 -0
  132. package/dist/service/terminal-service.js +52 -0
  133. package/dist/service/workbench-event-service.d.ts +21 -0
  134. package/dist/service/workbench-event-service.js +53 -0
  135. package/dist/service/workbench-manifest-service.d.ts +89 -0
  136. package/dist/service/workbench-manifest-service.js +70 -0
  137. package/dist/service/workspace-change-service.d.ts +9 -0
  138. package/dist/service/workspace-change-service.js +86 -0
  139. package/dist/service/workspace-content-search-service.d.ts +14 -0
  140. package/dist/service/workspace-content-search-service.js +477 -0
  141. package/dist/service/workspace-domain-state.d.ts +75 -0
  142. package/dist/service/workspace-domain-state.js +40 -0
  143. package/dist/service/workspace-file-service.d.ts +17 -0
  144. package/dist/service/workspace-file-service.js +109 -0
  145. package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
  146. package/dist/service/workspace-queue-workbench-service.js +226 -0
  147. package/dist/service/workspace-service.d.ts +15 -0
  148. package/dist/service/workspace-service.js +67 -0
  149. package/dist/service/workspace-session-service.d.ts +23 -0
  150. package/dist/service/workspace-session-service.js +139 -0
  151. package/dist/service/workspace-upload-service.d.ts +37 -0
  152. package/dist/service/workspace-upload-service.js +240 -0
  153. package/dist/service/workspace-watch-service.d.ts +18 -0
  154. package/dist/service/workspace-watch-service.js +120 -0
  155. package/dist/service/workspace-workbench-service.d.ts +47 -0
  156. package/dist/service/workspace-workbench-service.js +223 -0
  157. package/dist/service.js +2 -3
  158. package/dist/storage.js +0 -1
  159. package/dist/supervisor.d.ts +1 -0
  160. package/dist/supervisor.js +154 -0
  161. package/dist/terminal.d.ts +1 -0
  162. package/dist/terminal.js +1 -2
  163. package/dist/util/node-operation-parsers.d.ts +92 -0
  164. package/dist/util/node-operation-parsers.js +382 -0
  165. package/dist/util/runner-native-session-parsers.d.ts +72 -0
  166. package/dist/util/runner-native-session-parsers.js +381 -0
  167. package/dist/util/secret-environment.d.ts +1 -0
  168. package/dist/util/secret-environment.js +25 -0
  169. package/dist/workspace.d.ts +62 -2
  170. package/dist/workspace.js +539 -57
  171. package/package.json +3 -2
  172. package/dist/capabilities.js.map +0 -1
  173. package/dist/claude-agent-sdk.js.map +0 -1
  174. package/dist/claude-channel.js.map +0 -1
  175. package/dist/codex-app-server.js.map +0 -1
  176. package/dist/config.js.map +0 -1
  177. package/dist/connector.js.map +0 -1
  178. package/dist/database.js.map +0 -1
  179. package/dist/event-buffer.js.map +0 -1
  180. package/dist/fake-runner.js.map +0 -1
  181. package/dist/health.js.map +0 -1
  182. package/dist/main.js.map +0 -1
  183. package/dist/migrations/v001.js.map +0 -1
  184. package/dist/migrations/v002.js.map +0 -1
  185. package/dist/native-session-history.js.map +0 -1
  186. package/dist/operational.js.map +0 -1
  187. package/dist/process-tree.js.map +0 -1
  188. package/dist/runner-command-engine.js.map +0 -1
  189. package/dist/runner-profiles.js.map +0 -1
  190. package/dist/runner-usage.js.map +0 -1
  191. package/dist/runtime-state.js.map +0 -1
  192. package/dist/service.js.map +0 -1
  193. package/dist/storage.js.map +0 -1
  194. package/dist/terminal.js.map +0 -1
  195. package/dist/workspace.js.map +0 -1
@@ -0,0 +1 @@
1
+ export declare function superviseNode(configPath: string, bootstrapEntrypoint: string): Promise<void>;
@@ -0,0 +1,154 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { access, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { loadNodeConfig, nodeDatabasePath } from './config.js';
5
+ import { nodeLog, platformCliInvocation } from './operational.js';
6
+ import { nodeUpgradeRequestPath } from './service/node-upgrade-coordinator.js';
7
+ const UPGRADE_EXIT_CODE = 75;
8
+ const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
9
+ export async function superviseNode(configPath, bootstrapEntrypoint) {
10
+ const config = await loadNodeConfig(configPath);
11
+ const dataDirectory = dirname(nodeDatabasePath(config, configPath));
12
+ const activePath = resolve(dataDirectory, 'node-active-entrypoint');
13
+ let entrypoint = await readActiveEntrypoint(activePath, bootstrapEntrypoint);
14
+ for (;;) {
15
+ const childResult = await runProcess(process.execPath, [entrypoint, 'run', '--config', configPath], true, { ...process.env, MAR_NODE_SUPERVISED: '1' });
16
+ if (childResult.signalled)
17
+ return;
18
+ const code = childResult.code;
19
+ if (code !== UPGRADE_EXIT_CODE) {
20
+ if (code === 0)
21
+ return;
22
+ nodeLog('node.supervisor.child-exited', { code });
23
+ await delay(1_000);
24
+ continue;
25
+ }
26
+ const requestPath = nodeUpgradeRequestPath(nodeDatabasePath(config, configPath));
27
+ let stopping = false;
28
+ try {
29
+ const request = JSON.parse(await readFile(requestPath, 'utf8'));
30
+ const targetVersion = request['targetVersion'];
31
+ if (request['schemaVersion'] !== 1 ||
32
+ typeof targetVersion !== 'string' ||
33
+ !STABLE_VERSION.test(targetVersion))
34
+ throw new Error('NODE_UPGRADE_REQUEST_INVALID');
35
+ const prefix = resolve(dataDirectory, 'node-versions', targetVersion);
36
+ await rm(prefix, { recursive: true, force: true });
37
+ await mkdir(prefix, { recursive: true, mode: 0o700 });
38
+ const registry = validatedRegistry(config.upgrade?.registryUrl);
39
+ const invocation = platformCliInvocation('npm', [
40
+ 'install',
41
+ '--prefix',
42
+ prefix,
43
+ '--ignore-scripts',
44
+ '--no-audit',
45
+ '--no-fund',
46
+ ...(registry === undefined ? [] : ['--registry', registry]),
47
+ `@myagentroam/node@${targetVersion}`
48
+ ]);
49
+ const installResult = await runProcess(invocation.command, invocation.args, false);
50
+ if (installResult.signalled) {
51
+ stopping = true;
52
+ throw new Error('NODE_UPGRADE_INSTALL_INTERRUPTED');
53
+ }
54
+ if (installResult.code !== 0)
55
+ throw new Error('NODE_UPGRADE_INSTALL_FAILED');
56
+ const rebuild = platformCliInvocation('npm', [
57
+ 'rebuild',
58
+ '--prefix',
59
+ prefix,
60
+ ...(registry === undefined ? [] : ['--registry', registry]),
61
+ 'node-pty'
62
+ ]);
63
+ const rebuildResult = await runProcess(rebuild.command, rebuild.args, false);
64
+ if (rebuildResult.signalled) {
65
+ stopping = true;
66
+ throw new Error('NODE_UPGRADE_INSTALL_INTERRUPTED');
67
+ }
68
+ if (rebuildResult.code !== 0)
69
+ throw new Error('NODE_UPGRADE_NATIVE_REBUILD_FAILED');
70
+ const nextEntrypoint = resolve(prefix, 'node_modules', '@myagentroam', 'node', 'dist', 'main.js');
71
+ await access(nextEntrypoint);
72
+ await writeAtomic(activePath, `${nextEntrypoint}\n`);
73
+ await writeResult(dataDirectory, {
74
+ state: 'SUCCEEDED',
75
+ targetVersion,
76
+ updatedAt: Date.now()
77
+ });
78
+ await rm(requestPath, { force: true });
79
+ entrypoint = nextEntrypoint;
80
+ }
81
+ catch (error) {
82
+ await writeResult(dataDirectory, {
83
+ state: 'FAILED',
84
+ errorCode: error instanceof Error ? error.message : 'NODE_UPGRADE_INSTALL_FAILED',
85
+ updatedAt: Date.now()
86
+ });
87
+ await rm(requestPath, { force: true });
88
+ }
89
+ if (stopping)
90
+ return;
91
+ }
92
+ }
93
+ async function runProcess(command, args, inherit, env = process.env) {
94
+ return new Promise((resolveProcess) => {
95
+ const child = spawn(command, args, {
96
+ windowsHide: true,
97
+ stdio: inherit ? 'inherit' : 'ignore',
98
+ env
99
+ });
100
+ let settled = false;
101
+ let signalled = false;
102
+ const forward = (signal) => {
103
+ signalled = true;
104
+ child.kill(signal);
105
+ };
106
+ const onSigint = () => forward('SIGINT');
107
+ const onSigterm = () => forward('SIGTERM');
108
+ const finish = (code) => {
109
+ if (settled)
110
+ return;
111
+ settled = true;
112
+ process.off('SIGINT', onSigint);
113
+ process.off('SIGTERM', onSigterm);
114
+ resolveProcess({ code, signalled });
115
+ };
116
+ process.once('SIGINT', onSigint);
117
+ process.once('SIGTERM', onSigterm);
118
+ child.once('error', () => finish(null));
119
+ child.once('close', finish);
120
+ });
121
+ }
122
+ async function readActiveEntrypoint(path, fallback) {
123
+ try {
124
+ const value = (await readFile(path, 'utf8')).trim();
125
+ await access(value);
126
+ return value;
127
+ }
128
+ catch {
129
+ return fallback;
130
+ }
131
+ }
132
+ async function writeResult(directory, result) {
133
+ await writeAtomic(resolve(directory, 'upgrade-result.json'), `${JSON.stringify(result)}\n`);
134
+ }
135
+ async function writeAtomic(path, contents) {
136
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
137
+ const temporary = `${path}.${process.pid}.tmp`;
138
+ await writeFile(temporary, contents, { mode: 0o600 });
139
+ await rename(temporary, path);
140
+ }
141
+ function validatedRegistry(value) {
142
+ if (value === undefined)
143
+ return undefined;
144
+ const url = new URL(value);
145
+ if (url.protocol !== 'https:' &&
146
+ !(url.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(url.hostname)))
147
+ throw new Error('NODE_UPGRADE_REGISTRY_INVALID');
148
+ if (url.username !== '' || url.password !== '' || url.search !== '' || url.hash !== '')
149
+ throw new Error('NODE_UPGRADE_REGISTRY_INVALID');
150
+ return url.toString().replace(/\/$/u, '');
151
+ }
152
+ function delay(milliseconds) {
153
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
154
+ }
@@ -98,6 +98,7 @@ export declare class TerminalManager {
98
98
  readonly cwd?: unknown;
99
99
  /** Resolved by NodeConnector; never trusted from a browser directly. */
100
100
  readonly workspaceId?: string | null;
101
+ readonly environment?: Readonly<Record<string, string>>;
101
102
  }): TerminalSummary;
102
103
  list(): readonly TerminalSummary[];
103
104
  get(terminalId: string): TerminalSummary;
package/dist/terminal.js CHANGED
@@ -93,7 +93,7 @@ export class TerminalManager {
93
93
  cols,
94
94
  rows,
95
95
  cwd: typeof input.cwd === 'string' ? input.cwd : this.cwd,
96
- env: process.env
96
+ env: { ...process.env, ...input.environment }
97
97
  });
98
98
  const createdAt = this.now();
99
99
  const terminal = {
@@ -414,4 +414,3 @@ function splitFrameData(value) {
414
414
  }
415
415
  return chunks;
416
416
  }
417
- //# sourceMappingURL=terminal.js.map
@@ -0,0 +1,92 @@
1
+ import type { AgentRunStatus, NodeCapabilities, NodeEnvelope } from '@myagentroam/protocol';
2
+ import type { NodeAgentRun, NodeConversationTurn, NodeMessageAttachmentInput } from '../database.js';
3
+ import type { NativeTranscriptToolCall } from '../native-session-history.js';
4
+ export type RunnerImageAttachment = NodeMessageAttachmentInput & {
5
+ readonly mime: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
6
+ };
7
+ export declare const DEFAULT_SESSION_PAGE_SIZE = 10;
8
+ export declare const MAX_SESSION_PAGE_SIZE = 100;
9
+ export declare function sessionPage<T extends {
10
+ readonly id: string;
11
+ readonly lastActivityAt: number;
12
+ readonly pinOrder?: number | null;
13
+ readonly externalDiscovered?: boolean;
14
+ }>(sessions: readonly T[], input: Record<string, unknown>): {
15
+ readonly sessions: readonly T[];
16
+ readonly nextCursor: string | null;
17
+ };
18
+ export declare function parseSessionPageCursor(cursor: string): {
19
+ readonly id: string;
20
+ readonly lastActivityAt: number;
21
+ readonly pinOrder: number | null;
22
+ readonly externalDiscovered: boolean;
23
+ } | undefined;
24
+ export declare function createSessionPageCursor(session: {
25
+ readonly id: string;
26
+ readonly lastActivityAt: number;
27
+ readonly pinOrder?: number | null;
28
+ readonly externalDiscovered?: boolean;
29
+ }): string;
30
+ export declare function compareSessionPageEntries(left: {
31
+ readonly id: string;
32
+ readonly lastActivityAt: number;
33
+ readonly pinOrder?: number | null;
34
+ readonly externalDiscovered?: boolean;
35
+ }, right: {
36
+ readonly id: string;
37
+ readonly lastActivityAt: number;
38
+ readonly pinOrder?: number | null;
39
+ readonly externalDiscovered?: boolean;
40
+ }): number;
41
+ export declare function compactRunnerText(value: string, limit: number): string;
42
+ export declare function compactRunnerValue(value: unknown, limit: number): {
43
+ readonly text: string;
44
+ readonly truncated: boolean;
45
+ };
46
+ export declare function codexTimestamp(value: unknown): number | null;
47
+ export declare function nativePageEnd(cursor: string | undefined, sessionId: string, fallback: number): number;
48
+ export declare function extractClaudeText(message: Record<string, unknown>): string;
49
+ export declare function extractClaudeThinkingSummaries(message: Record<string, unknown>): readonly string[];
50
+ export declare function extractClaudeToolUses(message: Record<string, unknown>): readonly {
51
+ readonly id: string;
52
+ readonly name: string;
53
+ readonly input: unknown;
54
+ readonly command?: string;
55
+ readonly inputTruncated: boolean;
56
+ }[];
57
+ export declare function extractClaudeToolResults(message: Record<string, unknown>): readonly {
58
+ readonly toolUseId: string;
59
+ readonly output: string;
60
+ readonly failed: boolean;
61
+ readonly truncated: boolean;
62
+ }[];
63
+ export declare function claudeContentBlocks(message: Record<string, unknown>): readonly Record<string, unknown>[];
64
+ export declare function isClaudeCommandTool(name: string): boolean;
65
+ export declare function nativeClaudeCommand(entry: NativeTranscriptToolCall): string;
66
+ export declare function boundedConversationItemId(prefix: string, value: string): string;
67
+ export declare function validUserAuthorization(value: unknown, envelope: NodeEnvelope, operation: string, nodeId: string | undefined, now?: number): boolean;
68
+ export declare function isTerminalRunStatus(status: AgentRunStatus): boolean;
69
+ export declare function validSessionOption(value: unknown): boolean;
70
+ export declare function validSessionEffort(value: unknown): boolean;
71
+ export declare function validSessionTitle(value: unknown): boolean;
72
+ export declare function sessionTitleFromFirstMessage(content: string): string;
73
+ export declare function parseComposerAttachmentUpload(input: Record<string, unknown>): {
74
+ readonly id: string;
75
+ readonly name: string;
76
+ readonly mime: string;
77
+ } | undefined;
78
+ export declare function parseComposerAttachmentIds(value: unknown): readonly string[];
79
+ export declare function validComposerAttachmentName(name: string): boolean;
80
+ export declare function isUuid(value: string): boolean;
81
+ export declare function parseMessageAttachments(value: unknown): readonly NodeMessageAttachmentInput[];
82
+ export declare function parseRunnerImageAttachments(value: unknown): readonly RunnerImageAttachment[];
83
+ export declare function isRunnerImageAttachment(attachment: NodeMessageAttachmentInput): attachment is RunnerImageAttachment;
84
+ export declare function isImageAttachment(attachment: NodeMessageAttachmentInput): boolean;
85
+ export declare function withNonImageAttachmentPrompt(input: string, paths: readonly string[]): string;
86
+ export declare function isCanonicalBase64(value: string): boolean;
87
+ export declare function validatedCapabilities(value: unknown): NodeCapabilities;
88
+ export declare function nodeConnectEndpoint(serverUrl: string): string;
89
+ export declare function nodeTerminalConnectEndpoint(serverUrl: string): string;
90
+ export declare function isPlainRecord(value: unknown): value is Record<string, unknown>;
91
+ export declare function isNodeAgentRun(value: unknown): value is NodeAgentRun;
92
+ export declare function isNodeConversationTurn(value: unknown): value is NodeConversationTurn;
@@ -0,0 +1,382 @@
1
+ import { nodeCapabilitiesSchema } from '@myagentroam/protocol';
2
+ const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
3
+ const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
4
+ const USER_AUTHORIZATION_CLOCK_SKEW_MS = 5_000;
5
+ const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\n本轮非图片附件已上传到工作区,请按需读取以下文件:\n';
6
+ const COMPOSER_IMAGE_MIME_TYPES = new Set([
7
+ 'image/png',
8
+ 'image/jpeg',
9
+ 'image/gif',
10
+ 'image/webp'
11
+ ]);
12
+ export const DEFAULT_SESSION_PAGE_SIZE = 10;
13
+ export const MAX_SESSION_PAGE_SIZE = 100;
14
+ export function sessionPage(sessions, input) {
15
+ const limit = typeof input.limit === 'number' &&
16
+ Number.isInteger(input.limit) &&
17
+ input.limit >= 1 &&
18
+ input.limit <= MAX_SESSION_PAGE_SIZE
19
+ ? input.limit
20
+ : DEFAULT_SESSION_PAGE_SIZE;
21
+ const cursor = typeof input.cursor === 'string' ? input.cursor : undefined;
22
+ const ordered = [...sessions].sort(compareSessionPageEntries);
23
+ const cursorEntry = cursor === undefined ? undefined : parseSessionPageCursor(cursor);
24
+ const afterCursor = cursor === undefined
25
+ ? ordered
26
+ : cursorEntry === undefined
27
+ ? []
28
+ : ordered.filter((session) => compareSessionPageEntries(session, cursorEntry) > 0);
29
+ const pinned = ordered.filter((session) => session.pinOrder !== null && session.pinOrder !== undefined);
30
+ const pinnedIndexes = new Map(pinned.map((session, index) => [session.id, index]));
31
+ const page = afterCursor.slice(0, limit).map((session) => {
32
+ const pinIndex = pinnedIndexes.get(session.id);
33
+ if (pinIndex === undefined)
34
+ return session;
35
+ return {
36
+ ...session,
37
+ canMovePinUp: pinIndex > 0,
38
+ canMovePinDown: pinIndex < pinned.length - 1
39
+ };
40
+ });
41
+ const last = page.at(-1);
42
+ return {
43
+ sessions: page,
44
+ nextCursor: last !== undefined && afterCursor.length > page.length ? createSessionPageCursor(last) : null
45
+ };
46
+ }
47
+ export function parseSessionPageCursor(cursor) {
48
+ try {
49
+ const value = JSON.parse(decodeURIComponent(cursor));
50
+ if (!Array.isArray(value) || value.length !== 3)
51
+ return undefined;
52
+ const [pinOrder, lastActivityAt, id] = value;
53
+ if ((pinOrder !== null && (typeof pinOrder !== 'number' || !Number.isFinite(pinOrder))) ||
54
+ (lastActivityAt !== null &&
55
+ (typeof lastActivityAt !== 'number' || !Number.isFinite(lastActivityAt))) ||
56
+ typeof id !== 'string')
57
+ return undefined;
58
+ return {
59
+ id,
60
+ lastActivityAt: lastActivityAt ?? 0,
61
+ pinOrder,
62
+ externalDiscovered: lastActivityAt !== null
63
+ };
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ export function createSessionPageCursor(session) {
70
+ return encodeURIComponent(JSON.stringify([
71
+ session.pinOrder ?? null,
72
+ session.externalDiscovered === false ? null : session.lastActivityAt,
73
+ session.id
74
+ ]));
75
+ }
76
+ export function compareSessionPageEntries(left, right) {
77
+ const leftPinned = left.pinOrder !== null && left.pinOrder !== undefined;
78
+ const rightPinned = right.pinOrder !== null && right.pinOrder !== undefined;
79
+ if (leftPinned !== rightPinned)
80
+ return leftPinned ? -1 : 1;
81
+ if (leftPinned && rightPinned)
82
+ return (left.pinOrder - right.pinOrder || left.id.localeCompare(right.id));
83
+ const leftFound = left.externalDiscovered !== false;
84
+ const rightFound = right.externalDiscovered !== false;
85
+ if (leftFound !== rightFound)
86
+ return leftFound ? -1 : 1;
87
+ return right.lastActivityAt - left.lastActivityAt || left.id.localeCompare(right.id, 'en');
88
+ }
89
+ export function compactRunnerText(value, limit) {
90
+ return value.length <= limit ? value : `${value.slice(0, Math.max(0, limit - 1))}…`;
91
+ }
92
+ export function compactRunnerValue(value, limit) {
93
+ if (value === undefined || value === null)
94
+ return { text: '', truncated: false };
95
+ let text;
96
+ if (typeof value === 'string')
97
+ text = value;
98
+ else {
99
+ try {
100
+ text = JSON.stringify(value);
101
+ }
102
+ catch {
103
+ return { text: '(无法显示)', truncated: false };
104
+ }
105
+ }
106
+ return { text: compactRunnerText(text, limit), truncated: text.length > limit };
107
+ }
108
+ export function codexTimestamp(value) {
109
+ if (typeof value === 'number' && Number.isFinite(value))
110
+ return value < 10_000_000_000 ? value * 1_000 : value;
111
+ if (typeof value === 'string') {
112
+ const timestamp = Date.parse(value);
113
+ return Number.isNaN(timestamp) ? null : timestamp;
114
+ }
115
+ return null;
116
+ }
117
+ export function nativePageEnd(cursor, sessionId, fallback) {
118
+ if (cursor === undefined)
119
+ return fallback;
120
+ try {
121
+ const value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
122
+ return value.sessionId === sessionId &&
123
+ Number.isInteger(value.end) &&
124
+ value.end >= 0
125
+ ? Math.min(value.end, fallback)
126
+ : fallback;
127
+ }
128
+ catch {
129
+ return fallback;
130
+ }
131
+ }
132
+ export function extractClaudeText(message) {
133
+ return claudeContentBlocks(message)
134
+ .flatMap((block) => block.type === 'text' && typeof block.text === 'string' ? [block.text] : [])
135
+ .join('');
136
+ }
137
+ export function extractClaudeThinkingSummaries(message) {
138
+ return claudeContentBlocks(message).flatMap((block) => {
139
+ // `showThinkingSummaries` asks the SDK/API for exactly these public
140
+ // summaries. Partial raw thinking events are intentionally not read.
141
+ if (block.type !== 'thinking' || typeof block.thinking !== 'string')
142
+ return [];
143
+ const text = compactRunnerText(block.thinking.trim(), 10_000);
144
+ return text.length === 0 ? [] : [text];
145
+ });
146
+ }
147
+ export function extractClaudeToolUses(message) {
148
+ return claudeContentBlocks(message).flatMap((block) => {
149
+ if (block.type !== 'tool_use' || typeof block.id !== 'string' || typeof block.name !== 'string')
150
+ return [];
151
+ const input = block.input;
152
+ const command = isPlainRecord(input) && typeof input.command === 'string' ? input.command : undefined;
153
+ return [
154
+ {
155
+ id: block.id,
156
+ name: block.name,
157
+ input,
158
+ ...(command === undefined ? {} : { command }),
159
+ inputTruncated: compactRunnerValue(input, 10_000).truncated
160
+ }
161
+ ];
162
+ });
163
+ }
164
+ export function extractClaudeToolResults(message) {
165
+ const parentId = typeof message.parent_tool_use_id === 'string' ? message.parent_tool_use_id : null;
166
+ const direct = compactRunnerValue(message.tool_use_result, 10_000);
167
+ if (parentId !== null && direct.text.length > 0)
168
+ return [
169
+ { toolUseId: parentId, output: direct.text, failed: false, truncated: direct.truncated }
170
+ ];
171
+ return claudeContentBlocks(message).flatMap((block) => {
172
+ if (block.type !== 'tool_result' || typeof block.tool_use_id !== 'string')
173
+ return [];
174
+ const result = compactRunnerValue(block.content, 10_000);
175
+ return [
176
+ {
177
+ toolUseId: block.tool_use_id,
178
+ output: result.text,
179
+ failed: block.is_error === true,
180
+ truncated: result.truncated
181
+ }
182
+ ];
183
+ });
184
+ }
185
+ export function claudeContentBlocks(message) {
186
+ const content = message.message;
187
+ if (!isPlainRecord(content) || !Array.isArray(content.content))
188
+ return [];
189
+ return content.content.filter(isPlainRecord);
190
+ }
191
+ export function isClaudeCommandTool(name) {
192
+ return name === 'Bash' || name === 'bash' || name === 'Shell';
193
+ }
194
+ export function nativeClaudeCommand(entry) {
195
+ if (isPlainRecord(entry.input) && typeof entry.input.command === 'string')
196
+ return compactRunnerText(entry.input.command, 10_000);
197
+ return entry.inputSummary ?? entry.toolName;
198
+ }
199
+ export function boundedConversationItemId(prefix, value) {
200
+ const candidate = `${prefix}:${value}`;
201
+ if (candidate.length <= 88)
202
+ return candidate;
203
+ return `${prefix}:${Buffer.from(value)
204
+ .toString('base64url')
205
+ .slice(0, 88 - prefix.length - 1)}`;
206
+ }
207
+ export function validUserAuthorization(value, envelope, operation, nodeId, now = Date.now()) {
208
+ if (typeof value !== 'object' || value === null || nodeId === undefined)
209
+ return false;
210
+ const authorization = value;
211
+ return (typeof authorization.userId === 'number' &&
212
+ Number.isSafeInteger(authorization.userId) &&
213
+ authorization.userId > 0 &&
214
+ typeof authorization.sessionHash === 'string' &&
215
+ /^[A-Za-z0-9_-]{43}$/.test(authorization.sessionHash) &&
216
+ authorization.nodeId === nodeId &&
217
+ authorization.operation === operation &&
218
+ authorization.requestId === envelope.id &&
219
+ typeof authorization.issuedAt === 'number' &&
220
+ typeof authorization.expiresAt === 'number' &&
221
+ Number.isSafeInteger(authorization.issuedAt) &&
222
+ Number.isSafeInteger(authorization.expiresAt) &&
223
+ authorization.issuedAt <= now + USER_AUTHORIZATION_CLOCK_SKEW_MS &&
224
+ authorization.expiresAt >= now - USER_AUTHORIZATION_CLOCK_SKEW_MS &&
225
+ authorization.expiresAt - authorization.issuedAt <= 15_000);
226
+ }
227
+ export function isTerminalRunStatus(status) {
228
+ return (status === 'SUCCEEDED' ||
229
+ status === 'FAILED' ||
230
+ status === 'CANCELLED' ||
231
+ status === 'INTERRUPTED');
232
+ }
233
+ export function validSessionOption(value) {
234
+ return (value === undefined ||
235
+ (typeof value === 'string' && value.trim().length > 0 && value.length <= 120));
236
+ }
237
+ export function validSessionEffort(value) {
238
+ return value === undefined || (typeof value === 'string' && value.length <= 120);
239
+ }
240
+ export function validSessionTitle(value) {
241
+ return (value === undefined ||
242
+ (typeof value === 'string' && value.trim().length > 0 && value.length <= 160));
243
+ }
244
+ export function sessionTitleFromFirstMessage(content) {
245
+ return content.trim().replace(/\s+/g, ' ').slice(0, 160);
246
+ }
247
+ export function parseComposerAttachmentUpload(input) {
248
+ if (input.composerAttachmentId === undefined && input.mime === undefined)
249
+ return undefined;
250
+ if (typeof input.composerAttachmentId !== 'string' ||
251
+ !isUuid(input.composerAttachmentId) ||
252
+ typeof input.name !== 'string' ||
253
+ !validComposerAttachmentName(input.name) ||
254
+ typeof input.mime !== 'string' ||
255
+ input.mime.length === 0 ||
256
+ input.mime.length > 120)
257
+ throw new Error('COMPOSER_ATTACHMENT_INVALID');
258
+ return { id: input.composerAttachmentId, name: input.name, mime: input.mime };
259
+ }
260
+ export function parseComposerAttachmentIds(value) {
261
+ if (value === undefined)
262
+ return [];
263
+ if (!Array.isArray(value) || value.length > 5 || value.some((id) => typeof id !== 'string'))
264
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
265
+ const ids = value;
266
+ if (ids.some((id) => !isUuid(id)) || new Set(ids).size !== ids.length)
267
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
268
+ return ids;
269
+ }
270
+ export function validComposerAttachmentName(name) {
271
+ return (name.trim().length > 0 &&
272
+ name.length <= 255 &&
273
+ name !== '.' &&
274
+ name !== '..' &&
275
+ !name.includes('/') &&
276
+ !name.includes('\\') &&
277
+ !Array.from(name).some((character) => {
278
+ const code = character.charCodeAt(0);
279
+ return code <= 31 || code === 127 || '<>:"|?*'.includes(character);
280
+ }));
281
+ }
282
+ export function isUuid(value) {
283
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
284
+ }
285
+ export function parseMessageAttachments(value) {
286
+ if (value === undefined)
287
+ return [];
288
+ if (!Array.isArray(value) || value.length > 5)
289
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
290
+ let totalBytes = 0;
291
+ return value.map((attachment) => {
292
+ if (!isPlainRecord(attachment))
293
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
294
+ if (typeof attachment.name !== 'string' ||
295
+ typeof attachment.mime !== 'string' ||
296
+ typeof attachment.dataBase64 !== 'string')
297
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
298
+ if (!validComposerAttachmentName(attachment.name) ||
299
+ attachment.mime.length === 0 ||
300
+ attachment.mime.length > 120 ||
301
+ !isCanonicalBase64(attachment.dataBase64))
302
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
303
+ const byteSize = Buffer.byteLength(attachment.dataBase64, 'base64');
304
+ if (byteSize === 0 || byteSize > MAX_COMPOSER_ATTACHMENT_BYTES)
305
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
306
+ totalBytes += byteSize;
307
+ if (totalBytes > MAX_COMPOSER_ATTACHMENTS_BYTES)
308
+ throw new Error('MESSAGE_ATTACHMENTS_INVALID');
309
+ return { name: attachment.name, mime: attachment.mime, dataBase64: attachment.dataBase64 };
310
+ });
311
+ }
312
+ export function parseRunnerImageAttachments(value) {
313
+ return parseMessageAttachments(value).map((attachment) => {
314
+ if (!isRunnerImageAttachment(attachment))
315
+ throw new Error('MESSAGE_ATTACHMENT_UNSUPPORTED');
316
+ return attachment;
317
+ });
318
+ }
319
+ export function isRunnerImageAttachment(attachment) {
320
+ return COMPOSER_IMAGE_MIME_TYPES.has(attachment.mime);
321
+ }
322
+ export function isImageAttachment(attachment) {
323
+ return attachment.mime.startsWith('image/');
324
+ }
325
+ export function withNonImageAttachmentPrompt(input, paths) {
326
+ if (paths.length === 0)
327
+ return input;
328
+ return `${input}${NON_IMAGE_ATTACHMENT_PROMPT_PREFIX}${paths
329
+ .map((path) => `- ${path}`)
330
+ .join('\n')}`;
331
+ }
332
+ export function isCanonicalBase64(value) {
333
+ return (value.length > 0 &&
334
+ value.length % 4 === 0 &&
335
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value));
336
+ }
337
+ export function validatedCapabilities(value) {
338
+ return nodeCapabilitiesSchema.parse(value);
339
+ }
340
+ export function nodeConnectEndpoint(serverUrl) {
341
+ const endpoint = new URL(serverUrl);
342
+ if (endpoint.protocol === 'http:')
343
+ endpoint.protocol = 'ws:';
344
+ if (endpoint.protocol === 'https:')
345
+ endpoint.protocol = 'wss:';
346
+ if (endpoint.protocol !== 'ws:' && endpoint.protocol !== 'wss:') {
347
+ throw new Error('SERVER_URL_INVALID');
348
+ }
349
+ endpoint.pathname = `${endpoint.pathname.replace(/\/$/, '')}/api/node/connect`;
350
+ endpoint.search = '';
351
+ endpoint.hash = '';
352
+ return endpoint.toString();
353
+ }
354
+ export function nodeTerminalConnectEndpoint(serverUrl) {
355
+ const endpoint = new URL(serverUrl);
356
+ if (endpoint.protocol === 'http:')
357
+ endpoint.protocol = 'ws:';
358
+ if (endpoint.protocol === 'https:')
359
+ endpoint.protocol = 'wss:';
360
+ if (endpoint.protocol !== 'ws:' && endpoint.protocol !== 'wss:') {
361
+ throw new Error('SERVER_URL_INVALID');
362
+ }
363
+ endpoint.pathname = `${endpoint.pathname.replace(/\/$/, '')}/api/node/terminal-connect`;
364
+ endpoint.search = '';
365
+ endpoint.hash = '';
366
+ return endpoint.toString();
367
+ }
368
+ export function isPlainRecord(value) {
369
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
370
+ }
371
+ export function isNodeAgentRun(value) {
372
+ return (isPlainRecord(value) &&
373
+ typeof value.id === 'string' &&
374
+ typeof value.sessionId === 'string' &&
375
+ typeof value.status === 'string');
376
+ }
377
+ export function isNodeConversationTurn(value) {
378
+ return (isPlainRecord(value) &&
379
+ typeof value.id === 'string' &&
380
+ typeof value.sessionId === 'string' &&
381
+ Array.isArray(value.items));
382
+ }