@myagentroam/node 0.9.0 → 0.9.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.
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +50 -25
- package/dist/control-outbound-scheduler.d.ts +4 -1
- package/dist/control-outbound-scheduler.js +39 -6
- package/dist/database.d.ts +0 -19
- package/dist/database.js +1 -62
- package/dist/event-buffer.d.ts +2 -5
- package/dist/event-buffer.js +3 -12
- package/dist/native-session-history.js +105 -26
- package/dist/runner/abstract-runner.d.ts +1 -1
- package/dist/runner/abstract-runner.js +2 -2
- package/dist/runner/claude/managed-run-controller.d.ts +0 -1
- package/dist/runner/claude/managed-run-controller.js +32 -17
- package/dist/runner/claude-code-runner.js +3 -4
- package/dist/runner/codex/conversation-parser.d.ts +3 -3
- package/dist/runner/codex/conversation-parser.js +33 -12
- package/dist/runner/codex/managed-run-controller.d.ts +2 -2
- package/dist/runner/codex/managed-run-controller.js +26 -10
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +40 -10
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +16 -16
- package/dist/runner/opencode/managed-run-controller.d.ts +8 -2
- package/dist/runner/opencode/managed-run-controller.js +94 -22
- package/dist/runner/opencode-runner.d.ts +3 -0
- package/dist/runner/opencode-runner.js +25 -0
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -17
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-state.d.ts +5 -12
- package/dist/runtime-state.js +32 -46
- package/dist/service/attachment-domain-state.d.ts +2 -0
- package/dist/service/attachment-domain-state.js +2 -0
- package/dist/service/conversation-history-service.js +55 -24
- package/dist/service/conversation-segment-service.d.ts +20 -0
- package/dist/service/conversation-segment-service.js +156 -0
- package/dist/service/external-session-resume-service.js +9 -0
- package/dist/service/fake-managed-run-service.d.ts +0 -1
- package/dist/service/fake-managed-run-service.js +0 -2
- package/dist/service/mcp-installation-verifier.js +1 -1
- package/dist/service/native-session-projection-service.js +5 -0
- package/dist/service/native-session-watch-service.d.ts +1 -0
- package/dist/service/native-session-watch-service.js +21 -4
- package/dist/service/node-connection-lifecycle-service.d.ts +0 -2
- package/dist/service/node-connection-lifecycle-service.js +0 -1
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-control-message-service.d.ts +0 -1
- package/dist/service/node-control-message-service.js +9 -6
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/node-workspace-coordinator.d.ts +1 -4
- package/dist/service/node-workspace-coordinator.js +2 -12
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +51 -17
- package/dist/service/run-event-service.d.ts +2 -0
- package/dist/service/run-event-service.js +77 -2
- package/dist/service/run-workbench-service.d.ts +1 -1
- package/dist/service/run-workbench-service.js +1 -5
- package/dist/service/runner-interaction-service.d.ts +1 -2
- package/dist/service/runner-interaction-service.js +1 -4
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-context-service.d.ts +1 -0
- package/dist/service/session-context-service.js +14 -0
- package/dist/service/session-domain-state.d.ts +1 -0
- package/dist/service/session-domain-state.js +1 -0
- package/dist/service/session-message-service.js +5 -10
- package/dist/service/session-presentation-service.d.ts +1 -0
- package/dist/service/session-presentation-service.js +8 -1
- package/dist/service/session-query-service.d.ts +4 -0
- package/dist/service/session-query-service.js +6 -1
- package/dist/service/skill-directory-service.d.ts +3 -1
- package/dist/service/skill-directory-service.js +31 -30
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.d.ts +1 -1
- package/dist/service/skill-node-operation-service.js +6 -3
- package/dist/service/workbench-event-service.d.ts +1 -13
- package/dist/service/workbench-event-service.js +2 -36
- package/dist/service/workbench-manifest-service.d.ts +0 -1
- package/dist/service/workbench-manifest-service.js +0 -1
- package/dist/service/workspace-domain-state.d.ts +1 -0
- package/dist/service/workspace-git-exclude-service.d.ts +4 -0
- package/dist/service/workspace-git-exclude-service.js +119 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +1 -0
- package/dist/service/workspace-queue-workbench-service.js +4 -2
- package/dist/service/workspace-service.js +2 -0
- package/dist/service/workspace-upload-service.d.ts +2 -0
- package/dist/service/workspace-upload-service.js +28 -6
- package/dist/service/workspace-workbench-service.d.ts +0 -3
- package/dist/service/workspace-workbench-service.js +0 -17
- package/dist/supervisor.js +2 -1
- package/dist/terminal.js +1 -1
- package/dist/util/node-operation-parsers.js +2 -2
- package/dist/util/runner-error.d.ts +6 -0
- package/dist/util/runner-error.js +52 -0
- package/dist/util/runner-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +48 -20
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
1
|
+
import { type NodeEnvelope, type WorkbenchEventCategory } from '@myagentroam/protocol';
|
|
3
2
|
export declare class WorkbenchEventService {
|
|
4
3
|
private readonly send;
|
|
5
4
|
private readonly accepts;
|
|
6
|
-
private readonly buffer;
|
|
7
5
|
private sequence;
|
|
8
|
-
private readonly history;
|
|
9
6
|
constructor(send: (envelope: NodeEnvelope) => void, accepts: (category: WorkbenchEventCategory, payload: unknown) => boolean);
|
|
10
7
|
emit(category: WorkbenchEventCategory, payload: unknown): void;
|
|
11
|
-
read(after: number): {
|
|
12
|
-
readonly events: readonly WorkbenchEvent[];
|
|
13
|
-
readonly latestSequence: number;
|
|
14
|
-
readonly resyncRequired: boolean;
|
|
15
|
-
};
|
|
16
|
-
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
17
8
|
snapshotSequence(): number;
|
|
18
|
-
acknowledge(replyTo: string | undefined): boolean;
|
|
19
|
-
replay(): void;
|
|
20
|
-
get pendingCount(): number;
|
|
21
9
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
2
|
export class WorkbenchEventService {
|
|
3
3
|
send;
|
|
4
4
|
accepts;
|
|
5
|
-
buffer = new ReliableWorkbenchEventBuffer();
|
|
6
5
|
sequence = 0;
|
|
7
|
-
history = [];
|
|
8
6
|
constructor(send, accepts) {
|
|
9
7
|
this.send = send;
|
|
10
8
|
this.accepts = accepts;
|
|
@@ -13,41 +11,9 @@ export class WorkbenchEventService {
|
|
|
13
11
|
if (!this.accepts(category, payload))
|
|
14
12
|
return;
|
|
15
13
|
const event = { sequence: ++this.sequence, category, payload };
|
|
16
|
-
this.
|
|
17
|
-
if (this.history.length > 1_000)
|
|
18
|
-
this.history.shift();
|
|
19
|
-
this.send(this.buffer.queue(event));
|
|
20
|
-
}
|
|
21
|
-
read(after) {
|
|
22
|
-
const first = this.history[0]?.sequence;
|
|
23
|
-
const reset = after > this.sequence || (first !== undefined && after < first - 1);
|
|
24
|
-
return {
|
|
25
|
-
events: reset ? [] : this.history.filter((event) => event.sequence > after),
|
|
26
|
-
latestSequence: this.sequence,
|
|
27
|
-
resyncRequired: reset
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
operations() {
|
|
31
|
-
return {
|
|
32
|
-
'workbench.events.read': (data) => {
|
|
33
|
-
const afterValue = data?.after;
|
|
34
|
-
const after = typeof afterValue === 'number' && Number.isInteger(afterValue) && afterValue >= 0
|
|
35
|
-
? afterValue
|
|
36
|
-
: 0;
|
|
37
|
-
return this.read(after);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
14
|
+
this.send(createEnvelope('workbench.event', event));
|
|
40
15
|
}
|
|
41
16
|
snapshotSequence() {
|
|
42
17
|
return this.sequence;
|
|
43
18
|
}
|
|
44
|
-
acknowledge(replyTo) {
|
|
45
|
-
return this.buffer.acknowledge(replyTo);
|
|
46
|
-
}
|
|
47
|
-
replay() {
|
|
48
|
-
this.buffer.replay(this.send);
|
|
49
|
-
}
|
|
50
|
-
get pendingCount() {
|
|
51
|
-
return this.buffer.size;
|
|
52
|
-
}
|
|
53
19
|
}
|
|
@@ -21,7 +21,6 @@ export declare class WorkbenchManifestService {
|
|
|
21
21
|
reasonCode: null;
|
|
22
22
|
itemVersions: number[];
|
|
23
23
|
supportsSessionCursor: boolean;
|
|
24
|
-
supportsWorkspaceActivityCursor: boolean;
|
|
25
24
|
supportsHistoryPaging: boolean;
|
|
26
25
|
supportsItemFinalSnapshots: boolean;
|
|
27
26
|
maxPageSize: number;
|
|
@@ -42,6 +42,7 @@ export interface CompletedComposerAttachment {
|
|
|
42
42
|
readonly name: string;
|
|
43
43
|
readonly mime: string;
|
|
44
44
|
readonly size: number;
|
|
45
|
+
readonly timer: NodeJS.Timeout;
|
|
45
46
|
}
|
|
46
47
|
export declare class WorkspaceDomainState {
|
|
47
48
|
readonly uploads: Map<string, ActiveWorkspaceUpload>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function ensureWorkspaceGitExclude(workspace: string): Promise<void>;
|
|
2
|
+
export declare function updateWorkspaceSkillGitExclude(workspace: string, name: string, add: boolean): Promise<void>;
|
|
3
|
+
export declare function rewriteWorkspaceGitExclude(workspace: string, names: ReadonlySet<string>): Promise<void>;
|
|
4
|
+
export declare function hasBrokenWorkspaceSkillGitExclude(workspace: string, names: readonly string[]): Promise<boolean>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
const MAR_TEMP_DIRECTORY = '/.mar/';
|
|
7
|
+
const BLOCK_BEGIN = '# BEGIN MyAgentRoam';
|
|
8
|
+
const BLOCK_END = '# END MyAgentRoam';
|
|
9
|
+
const LEGACY_BLOCK_BEGIN = '# BEGIN MyAgentRoam Skills';
|
|
10
|
+
const LEGACY_BLOCK_END = '# END MyAgentRoam Skills';
|
|
11
|
+
export async function ensureWorkspaceGitExclude(workspace) {
|
|
12
|
+
await mutateGitExclude(workspace, (entries) => entries.add(MAR_TEMP_DIRECTORY));
|
|
13
|
+
}
|
|
14
|
+
export async function updateWorkspaceSkillGitExclude(workspace, name, add) {
|
|
15
|
+
await mutateGitExclude(workspace, (entries) => {
|
|
16
|
+
entries.add(MAR_TEMP_DIRECTORY);
|
|
17
|
+
for (const root of ['.agents', '.claude']) {
|
|
18
|
+
const line = `/${root}/skills/${name}/`;
|
|
19
|
+
if (add)
|
|
20
|
+
entries.add(line);
|
|
21
|
+
else
|
|
22
|
+
entries.delete(line);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function rewriteWorkspaceGitExclude(workspace, names) {
|
|
27
|
+
await mutateGitExclude(workspace, (entries) => {
|
|
28
|
+
entries.clear();
|
|
29
|
+
entries.add(MAR_TEMP_DIRECTORY);
|
|
30
|
+
for (const name of names)
|
|
31
|
+
for (const root of ['.agents', '.claude'])
|
|
32
|
+
entries.add(`/${root}/skills/${name}/`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function hasBrokenWorkspaceSkillGitExclude(workspace, names) {
|
|
36
|
+
if (!(await exists(path.join(workspace, '.git'))))
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
const exclude = await resolveGitExclude(workspace);
|
|
40
|
+
const content = await readFile(exclude, 'utf8');
|
|
41
|
+
return names
|
|
42
|
+
.flatMap((name) => [`/.agents/skills/${name}/`, `/.claude/skills/${name}/`])
|
|
43
|
+
.some((entry) => !content.includes(entry));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function mutateGitExclude(workspace, mutate) {
|
|
50
|
+
if (!(await exists(path.join(workspace, '.git'))))
|
|
51
|
+
return;
|
|
52
|
+
const exclude = await resolveGitExclude(workspace);
|
|
53
|
+
await mkdir(path.dirname(exclude), { recursive: true });
|
|
54
|
+
let content = '';
|
|
55
|
+
try {
|
|
56
|
+
const excludeStat = await lstat(exclude);
|
|
57
|
+
if (!excludeStat.isFile() || excludeStat.isSymbolicLink())
|
|
58
|
+
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
59
|
+
content = await readFile(exclude, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!isMissing(error))
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const pattern = managedBlockPattern();
|
|
66
|
+
const existing = [...content.matchAll(pattern)].map((match) => match[0]).join('\n');
|
|
67
|
+
const entries = new Set(existing.split(/\r?\n/u).filter((line) => line.startsWith('/.')));
|
|
68
|
+
mutate(entries);
|
|
69
|
+
const block = `${BLOCK_BEGIN}\n${[...entries].sort().join('\n')}\n${BLOCK_END}\n`;
|
|
70
|
+
const next = content.replace(pattern, '').replace(/\s*$/u, '\n') + block;
|
|
71
|
+
const staging = path.join(path.dirname(exclude), `.mar-git-exclude-${randomUUID()}.tmp`);
|
|
72
|
+
try {
|
|
73
|
+
await writeFile(staging, next, { encoding: 'utf8', flag: 'wx' });
|
|
74
|
+
await rename(staging, exclude);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
await rm(staging, { force: true });
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function resolveGitExclude(workspace) {
|
|
82
|
+
try {
|
|
83
|
+
const result = await promisify(execFile)('git', ['-C', workspace, 'rev-parse', '--git-path', 'info/exclude', '--git-common-dir'], { timeout: 5_000, windowsHide: true });
|
|
84
|
+
const [excludeOutput, commonOutput] = result.stdout.trim().split(/\r?\n/u);
|
|
85
|
+
if (!excludeOutput || !commonOutput)
|
|
86
|
+
throw new Error();
|
|
87
|
+
const exclude = path.resolve(workspace, excludeOutput);
|
|
88
|
+
const common = path.resolve(workspace, commonOutput);
|
|
89
|
+
const relative = path.relative(common, exclude);
|
|
90
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
91
|
+
throw new Error();
|
|
92
|
+
return exclude;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function exists(value) {
|
|
99
|
+
try {
|
|
100
|
+
await lstat(value);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (isMissing(error))
|
|
105
|
+
return false;
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function isMissing(error) {
|
|
110
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
111
|
+
}
|
|
112
|
+
function escapeRegExp(value) {
|
|
113
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
114
|
+
}
|
|
115
|
+
function managedBlockPattern() {
|
|
116
|
+
const current = `${escapeRegExp(BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp(BLOCK_END)}(?:\\r?\\n)?`;
|
|
117
|
+
const legacy = `${escapeRegExp(LEGACY_BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp(LEGACY_BLOCK_END)}(?:\\r?\\n)?`;
|
|
118
|
+
return new RegExp(`(?:${current}|${legacy})`, 'gu');
|
|
119
|
+
}
|
|
@@ -23,6 +23,7 @@ export interface WorkspaceQueueWorkbenchServiceOptions {
|
|
|
23
23
|
readonly runtime: NodeRuntimeState;
|
|
24
24
|
readonly emit: (workspaceId: string, sessionId: string) => void;
|
|
25
25
|
readonly clearImages: (runId: string) => void;
|
|
26
|
+
readonly cleanupAttachments: (runId: string) => void;
|
|
26
27
|
readonly emitRun: (runId: string, eventType: string, payload: unknown, status?: 'FAILED' | 'CANCELLED') => void;
|
|
27
28
|
readonly emitConversation: (turn: unknown) => void;
|
|
28
29
|
readonly control: (operation: string, payload: Record<string, unknown>) => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainRecord } from '../runner/codex/conversation-parser.js';
|
|
2
2
|
import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
|
|
3
3
|
import { withNonImageAttachmentPrompt } from '../runner/codex/conversation-parser.js';
|
|
4
|
+
import { safeErrorCode } from '../util/safe-error.js';
|
|
4
5
|
export class WorkspaceQueueWorkbenchService {
|
|
5
6
|
options;
|
|
6
7
|
starts = new Map();
|
|
@@ -79,6 +80,7 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
79
80
|
this.options.runtime.deleteQueuedRun(run.id);
|
|
80
81
|
this.remove(run.id);
|
|
81
82
|
this.options.clearImages(run.id);
|
|
83
|
+
this.options.cleanupAttachments(run.id);
|
|
82
84
|
this.options.emit(run.workspaceId, run.sessionId);
|
|
83
85
|
return { queue: this.present(run.workspaceId, run.sessionId) };
|
|
84
86
|
}
|
|
@@ -183,7 +185,7 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
183
185
|
});
|
|
184
186
|
}
|
|
185
187
|
catch (error) {
|
|
186
|
-
this.options.emitRun(run.id, 'run.rejected', { code: error
|
|
188
|
+
this.options.emitRun(run.id, 'run.rejected', { code: safeErrorCode(error, 'QUEUE_START_FAILED') }, 'FAILED');
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
present(workspaceId, sessionId) {
|
|
@@ -203,7 +205,7 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
203
205
|
{
|
|
204
206
|
run,
|
|
205
207
|
sessionId: run.sessionId,
|
|
206
|
-
sessionTitle: queuedSession?.customTitle ?? queuedSession?.runnerTitle ?? '
|
|
208
|
+
sessionTitle: queuedSession?.customTitle ?? queuedSession?.runnerTitle ?? 'Untitled session',
|
|
207
209
|
content: this.options.runtime.queuedMessageText(run.id)
|
|
208
210
|
}
|
|
209
211
|
];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rmdir } from 'node:fs/promises';
|
|
2
2
|
import { createWorkspaceDirectory, inspectWorkspace, listWorkspaceDirectories } from '../workspace.js';
|
|
3
|
+
import { ensureWorkspaceGitExclude } from './workspace-git-exclude-service.js';
|
|
3
4
|
export class WorkspaceService {
|
|
4
5
|
database;
|
|
5
6
|
config;
|
|
@@ -31,6 +32,7 @@ export class WorkspaceService {
|
|
|
31
32
|
? (createdPath = await createWorkspaceDirectory(input.path, input.createDirectoryName, this.config().allowedRoots))
|
|
32
33
|
: input.path;
|
|
33
34
|
const inspected = await inspectWorkspace(path, this.config().allowedRoots);
|
|
35
|
+
await ensureWorkspaceGitExclude(inspected.path);
|
|
34
36
|
return {
|
|
35
37
|
workspace: this.database().saveWorkspace({
|
|
36
38
|
path: inspected.path,
|
|
@@ -23,9 +23,11 @@ export declare class WorkspaceUploadService {
|
|
|
23
23
|
complete(uploadId: unknown): Promise<UploadStatus>;
|
|
24
24
|
cancel(uploadId: unknown): Promise<void>;
|
|
25
25
|
resolve(workspaceId: string, ids: readonly string[]): Promise<readonly ResolvedComposerAttachment[]>;
|
|
26
|
+
consume(ids: readonly string[]): Promise<void>;
|
|
26
27
|
shutdown(): Promise<void>;
|
|
27
28
|
private require;
|
|
28
29
|
private remove;
|
|
30
|
+
private removeCompleted;
|
|
29
31
|
private attachmentTarget;
|
|
30
32
|
}
|
|
31
33
|
interface UploadStatus {
|
|
@@ -133,16 +133,25 @@ export class WorkspaceUploadService {
|
|
|
133
133
|
clearTimeout(upload.timer);
|
|
134
134
|
this.state.uploads.delete(upload.id);
|
|
135
135
|
this.onCompleted(upload.workspaceId);
|
|
136
|
-
if (upload.composerAttachment !== undefined)
|
|
137
|
-
|
|
136
|
+
if (upload.composerAttachment !== undefined) {
|
|
137
|
+
const attachmentId = upload.composerAttachment.id;
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
const attachment = this.state.composerAttachments.get(attachmentId);
|
|
140
|
+
if (attachment !== undefined)
|
|
141
|
+
void this.removeCompleted(attachment);
|
|
142
|
+
}, TTL_MS);
|
|
143
|
+
timer.unref();
|
|
144
|
+
this.state.composerAttachments.set(attachmentId, {
|
|
138
145
|
id: upload.composerAttachment.id,
|
|
139
146
|
workspaceId: upload.workspaceId,
|
|
140
147
|
path: upload.path,
|
|
141
148
|
targetPath: upload.targetPath,
|
|
142
149
|
name: upload.composerAttachment.name,
|
|
143
150
|
mime: upload.composerAttachment.mime,
|
|
144
|
-
size: upload.size
|
|
151
|
+
size: upload.size,
|
|
152
|
+
timer
|
|
145
153
|
});
|
|
154
|
+
}
|
|
146
155
|
return {
|
|
147
156
|
uploadId: upload.id,
|
|
148
157
|
path: upload.path,
|
|
@@ -159,8 +168,7 @@ export class WorkspaceUploadService {
|
|
|
159
168
|
const attachment = this.state.composerAttachments.get(uploadId);
|
|
160
169
|
if (attachment === undefined)
|
|
161
170
|
throw new Error('UPLOAD_NOT_FOUND');
|
|
162
|
-
this.
|
|
163
|
-
await rm(dirname(attachment.targetPath), { recursive: true, force: true });
|
|
171
|
+
await this.removeCompleted(attachment);
|
|
164
172
|
}
|
|
165
173
|
async resolve(workspaceId, ids) {
|
|
166
174
|
return Promise.all(ids.map(async (id) => {
|
|
@@ -184,10 +192,17 @@ export class WorkspaceUploadService {
|
|
|
184
192
|
};
|
|
185
193
|
}));
|
|
186
194
|
}
|
|
195
|
+
async consume(ids) {
|
|
196
|
+
await Promise.all(ids.map(async (id) => {
|
|
197
|
+
const attachment = this.state.composerAttachments.get(id);
|
|
198
|
+
if (attachment !== undefined)
|
|
199
|
+
await this.removeCompleted(attachment);
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
187
202
|
async shutdown() {
|
|
188
203
|
await Promise.allSettled([...this.state.uploads.values()].map((upload) => this.remove(upload)));
|
|
189
204
|
for (const attachment of this.state.composerAttachments.values())
|
|
190
|
-
await
|
|
205
|
+
await this.removeCompleted(attachment);
|
|
191
206
|
this.state.composerAttachments.clear();
|
|
192
207
|
}
|
|
193
208
|
require(id) {
|
|
@@ -206,6 +221,13 @@ export class WorkspaceUploadService {
|
|
|
206
221
|
if (upload.composerAttachment !== undefined)
|
|
207
222
|
await rm(dirname(upload.targetPath), { recursive: true, force: true }).catch(() => undefined);
|
|
208
223
|
}
|
|
224
|
+
async removeCompleted(attachment) {
|
|
225
|
+
if (this.state.composerAttachments.get(attachment.id) !== attachment)
|
|
226
|
+
return;
|
|
227
|
+
this.state.composerAttachments.delete(attachment.id);
|
|
228
|
+
clearTimeout(attachment.timer);
|
|
229
|
+
await rm(dirname(attachment.targetPath), { recursive: true, force: true }).catch(() => undefined);
|
|
230
|
+
}
|
|
209
231
|
async attachmentTarget(workspacePath, id, name) {
|
|
210
232
|
const mar = resolve(workspacePath, '.mar');
|
|
211
233
|
await mkdir(mar, { recursive: true, mode: 0o700 });
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
2
2
|
import type { NodeWorkspace } from '../database.js';
|
|
3
|
-
import { NodeRuntimeState } from '../runtime-state.js';
|
|
4
3
|
import { WorkspaceChangeService } from './workspace-change-service.js';
|
|
5
4
|
import { WorkspaceFileService } from './workspace-file-service.js';
|
|
6
5
|
import { WorkspaceUploadService } from './workspace-upload-service.js';
|
|
7
6
|
import { WorkspaceWatchService } from './workspace-watch-service.js';
|
|
8
7
|
export interface WorkspaceWorkbenchServiceOptions {
|
|
9
8
|
readonly requireWorkspace: (workspaceId: unknown) => NodeWorkspace;
|
|
10
|
-
readonly runtime: NodeRuntimeState;
|
|
11
9
|
readonly watch: WorkspaceWatchService;
|
|
12
10
|
readonly files: WorkspaceFileService;
|
|
13
11
|
readonly uploads: WorkspaceUploadService;
|
|
@@ -20,7 +18,6 @@ export declare class WorkspaceWorkbenchService {
|
|
|
20
18
|
private readonly contentSearch;
|
|
21
19
|
constructor(options: WorkspaceWorkbenchServiceOptions);
|
|
22
20
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
23
|
-
private activity;
|
|
24
21
|
private watch;
|
|
25
22
|
private unwatch;
|
|
26
23
|
private listFiles;
|
|
@@ -9,7 +9,6 @@ export class WorkspaceWorkbenchService {
|
|
|
9
9
|
}
|
|
10
10
|
operations() {
|
|
11
11
|
return {
|
|
12
|
-
'workspace.activity.read': (data) => this.activity(record(data)),
|
|
13
12
|
'workspace.watch': (data) => this.watch(record(data)),
|
|
14
13
|
'workspace.unwatch': (data) => this.unwatch(record(data)),
|
|
15
14
|
'workspace.files.list': (data) => this.listFiles(record(data)),
|
|
@@ -35,20 +34,6 @@ export class WorkspaceWorkbenchService {
|
|
|
35
34
|
'workspace.diff': (data) => this.diff(record(data))
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
|
-
activity(input) {
|
|
39
|
-
if (typeof input.workspaceId !== 'string' ||
|
|
40
|
-
(input.cursor !== undefined && typeof input.cursor !== 'string') ||
|
|
41
|
-
(input.limit !== undefined &&
|
|
42
|
-
(!Number.isInteger(input.limit) ||
|
|
43
|
-
input.limit < 1 ||
|
|
44
|
-
input.limit > 500)))
|
|
45
|
-
throw new Error('EVENT_CURSOR_INVALID');
|
|
46
|
-
return this.options.runtime.listWorkspaceActivity({
|
|
47
|
-
workspaceId: input.workspaceId,
|
|
48
|
-
...(typeof input.cursor === 'string' ? { cursor: input.cursor } : {}),
|
|
49
|
-
...(typeof input.limit === 'number' ? { limit: input.limit } : {})
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
37
|
async watch(input) {
|
|
53
38
|
if (typeof input.workspaceId !== 'string' ||
|
|
54
39
|
typeof input.watchId !== 'string' ||
|
|
@@ -143,8 +128,6 @@ export class WorkspaceWorkbenchService {
|
|
|
143
128
|
(input.state !== 'STAGED' && input.state !== 'UNSTAGED' && input.state !== 'UNTRACKED'))
|
|
144
129
|
throw new Error('GIT_CHANGE_RESTORE_UNAVAILABLE');
|
|
145
130
|
const workspace = this.gitWorkspace(input.workspaceId);
|
|
146
|
-
if (this.options.runtime.hasActiveWorkspaceLease(workspace.id))
|
|
147
|
-
throw new Error('WORKSPACE_BUSY');
|
|
148
131
|
const repository = await resolveGitRepository(workspace.path, input.repositoryPath ?? '');
|
|
149
132
|
await restoreCurrentChange(repository, input.path, input.state);
|
|
150
133
|
this.options.invalidate(workspace.id);
|
package/dist/supervisor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { loadNodeConfig, nodeDatabasePath } from './config.js';
|
|
5
5
|
import { nodeLog, platformCliInvocation } from './operational.js';
|
|
6
6
|
import { nodeUpgradeRequestPath } from './service/node-upgrade-coordinator.js';
|
|
7
|
+
import { safeErrorCode } from './util/safe-error.js';
|
|
7
8
|
const UPGRADE_EXIT_CODE = 75;
|
|
8
9
|
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
9
10
|
export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
@@ -73,7 +74,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
73
74
|
catch (error) {
|
|
74
75
|
await writeResult(dataDirectory, {
|
|
75
76
|
state: 'FAILED',
|
|
76
|
-
errorCode: error
|
|
77
|
+
errorCode: safeErrorCode(error, 'NODE_UPGRADE_INSTALL_FAILED'),
|
|
77
78
|
updatedAt: Date.now()
|
|
78
79
|
});
|
|
79
80
|
await rm(requestPath, { force: true });
|
package/dist/terminal.js
CHANGED
|
@@ -2,7 +2,7 @@ import { nodeCapabilitiesSchema } from '@myagentroam/protocol';
|
|
|
2
2
|
const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
3
3
|
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
4
4
|
const USER_AUTHORIZATION_CLOCK_SKEW_MS = 5_000;
|
|
5
|
-
const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\n
|
|
5
|
+
const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\nNon-image attachments for this turn were uploaded to the Workspace. Read these files as needed:\n';
|
|
6
6
|
const COMPOSER_IMAGE_MIME_TYPES = new Set([
|
|
7
7
|
'image/png',
|
|
8
8
|
'image/jpeg',
|
|
@@ -100,7 +100,7 @@ export function compactRunnerValue(value, limit) {
|
|
|
100
100
|
text = JSON.stringify(value);
|
|
101
101
|
}
|
|
102
102
|
catch {
|
|
103
|
-
return { text: '
|
|
103
|
+
return { text: '(Unable to display)', truncated: false };
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
return { text: compactRunnerText(text, limit), truncated: text.length > limit };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const MAX_RUNNER_ERROR_MESSAGE = 1000;
|
|
2
|
+
export function runnerErrorProjection(error, fallbackCode) {
|
|
3
|
+
const httpStatus = findHttpStatus(error);
|
|
4
|
+
const message = findMessage(error) ?? fallbackCode;
|
|
5
|
+
return {
|
|
6
|
+
code: httpStatus === null ? fallbackCode : `HTTP_${httpStatus}`,
|
|
7
|
+
message: message.slice(0, MAX_RUNNER_ERROR_MESSAGE),
|
|
8
|
+
httpStatus
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function findHttpStatus(value, depth = 0) {
|
|
12
|
+
if (depth > 3 || value === null || typeof value !== 'object')
|
|
13
|
+
return null;
|
|
14
|
+
const record = value;
|
|
15
|
+
for (const key of ['status', 'statusCode', 'status_code', 'httpStatus', 'http_status']) {
|
|
16
|
+
const candidate = record[key];
|
|
17
|
+
if (((typeof candidate === 'number' && Number.isInteger(candidate)) ||
|
|
18
|
+
(typeof candidate === 'string' && /^\d{3}$/u.test(candidate))) &&
|
|
19
|
+
Number(candidate) >= 100 &&
|
|
20
|
+
Number(candidate) <= 599)
|
|
21
|
+
return Number(candidate);
|
|
22
|
+
}
|
|
23
|
+
const match = findMessage(value)?.match(/(?:^|\b(?:http(?:\s+status)?|status(?:\s+code)?|error)\s*[:=_-]?\s*)([1-5]\d{2})\b/iu);
|
|
24
|
+
if (match !== null && match !== undefined)
|
|
25
|
+
return Number(match[1]);
|
|
26
|
+
for (const key of ['error', 'cause', 'response', 'data']) {
|
|
27
|
+
const nested = findHttpStatus(record[key], depth + 1);
|
|
28
|
+
if (nested !== null)
|
|
29
|
+
return nested;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function findMessage(value, depth = 0) {
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return value.trim() || undefined;
|
|
36
|
+
if (value instanceof Error && value.message.trim())
|
|
37
|
+
return value.message.trim();
|
|
38
|
+
if (depth > 3 || value === null || typeof value !== 'object')
|
|
39
|
+
return undefined;
|
|
40
|
+
const record = value;
|
|
41
|
+
for (const key of ['message', 'error_description', 'detail', 'result']) {
|
|
42
|
+
const candidate = record[key];
|
|
43
|
+
if (typeof candidate === 'string' && candidate.trim())
|
|
44
|
+
return candidate.trim();
|
|
45
|
+
}
|
|
46
|
+
for (const key of ['error', 'cause', 'response', 'data']) {
|
|
47
|
+
const nested = findMessage(record[key], depth + 1);
|
|
48
|
+
if (nested !== undefined)
|
|
49
|
+
return nested;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
@@ -25,7 +25,6 @@ export declare function extractCodexThreads(result: unknown): readonly {
|
|
|
25
25
|
readonly title: string | undefined;
|
|
26
26
|
}[];
|
|
27
27
|
export declare function codexThreadActivity(result: unknown): SessionActivityState;
|
|
28
|
-
export declare function externalResumeFailureDetail(error: unknown): string;
|
|
29
28
|
export declare function codexActiveTurnId(result: unknown): string | undefined;
|
|
30
29
|
export declare function codexThreadCwd(value: unknown): string;
|
|
31
30
|
export declare function isWithinWorkspace(cwd: string, workspacePath: string): boolean;
|
|
@@ -46,8 +45,8 @@ export declare function nativeConversationPage(session: NodeAgentSession, histor
|
|
|
46
45
|
readonly nextCursor: string | null;
|
|
47
46
|
};
|
|
48
47
|
/**
|
|
49
|
-
* Claude JSONL
|
|
50
|
-
*
|
|
48
|
+
* Claude JSONL does not retain browser request IDs. While the Node process is alive, match current
|
|
49
|
+
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
51
50
|
*/
|
|
52
51
|
export declare function nativeUserClientMessageId(entry: Extract<NativeSessionHistory['items'][number], {
|
|
53
52
|
readonly kind: 'message';
|