@myagentroam/node 0.9.3 → 0.9.4
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/codex-app-server.d.ts +24 -0
- package/dist/codex-app-server.js +100 -5
- package/dist/connector.js +6 -2
- package/dist/database.d.ts +2 -0
- package/dist/runner/codex/managed-run-controller.d.ts +9 -0
- package/dist/runner/codex/managed-run-controller.js +57 -17
- package/dist/runner/codex-runner.d.ts +1 -0
- package/dist/runner/codex-runner.js +12 -1
- package/dist/runtime-state.d.ts +2 -0
- package/dist/runtime-state.js +11 -7
- package/dist/service/conversation-history-service.d.ts +8 -0
- package/dist/service/conversation-history-service.js +181 -9
- package/dist/service/conversation-segment-service.js +7 -1
- package/dist/service/native-session-watch-service.d.ts +8 -2
- package/dist/service/native-session-watch-service.js +28 -10
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-event-service.js +16 -5
- package/dist/service/session-lifecycle-service.js +4 -12
- package/dist/service/session-message-service.d.ts +1 -0
- package/dist/service/session-message-service.js +107 -78
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +12 -2
- package/dist/service/workspace-queue-workbench-service.js +7 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +27 -16
- package/dist/workspace.js +33 -8
- package/package.json +2 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { isImageAttachment, isRunnerImageAttachment, parseComposerAttachmentIds, parseMessageAttachments, sessionTitleFromFirstMessage, withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
|
|
2
3
|
import { parseSecretEnvironment } from '../util/secret-environment.js';
|
|
3
4
|
export class SessionMessageService {
|
|
4
5
|
options;
|
|
6
|
+
pendingMessages = new Map();
|
|
5
7
|
constructor(options) {
|
|
6
8
|
this.options = options;
|
|
7
9
|
}
|
|
@@ -19,20 +21,21 @@ export class SessionMessageService {
|
|
|
19
21
|
typeof input.content !== 'string' ||
|
|
20
22
|
input.content.length === 0)
|
|
21
23
|
throw new Error('MESSAGE_INVALID');
|
|
24
|
+
const clientMessageId = input.clientMessageId;
|
|
25
|
+
const content = input.content;
|
|
22
26
|
const inlineAttachments = parseMessageAttachments(input.attachments);
|
|
23
27
|
const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
|
|
24
28
|
const attachmentIds = parseComposerAttachmentIds(input.attachmentIds);
|
|
25
29
|
if (inlineAttachments.length > 0 && attachmentIds.length > 0)
|
|
26
30
|
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
27
|
-
const session =
|
|
28
|
-
this.options.emitSession(session);
|
|
31
|
+
const session = await this.options.resolve(input.sessionId);
|
|
29
32
|
const intent = input.deliveryIntent;
|
|
30
33
|
if (intent !== undefined &&
|
|
31
34
|
intent !== 'SEND' &&
|
|
32
35
|
intent !== 'QUEUE' &&
|
|
33
36
|
intent !== 'REPLACE_CURRENT')
|
|
34
37
|
throw new Error('MESSAGE_INVALID');
|
|
35
|
-
const existing = this.options.runtime.findMessageRun(session.id,
|
|
38
|
+
const existing = this.options.runtime.findMessageRun(session.id, clientMessageId);
|
|
36
39
|
if (existing !== undefined)
|
|
37
40
|
return {
|
|
38
41
|
session: this.options.present(session),
|
|
@@ -40,84 +43,110 @@ export class SessionMessageService {
|
|
|
40
43
|
delivery: this.options.runtime.isQueuedRun(existing.id) ? 'QUEUED' : 'STARTED',
|
|
41
44
|
idempotent: true
|
|
42
45
|
};
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
const pendingKey = `${session.id}\u0000${clientMessageId}`;
|
|
47
|
+
const pending = this.pendingMessages.get(pendingKey);
|
|
48
|
+
if (pending !== undefined)
|
|
49
|
+
return pending;
|
|
50
|
+
const operation = (async () => {
|
|
51
|
+
const runId = randomUUID();
|
|
52
|
+
let enqueued = false;
|
|
53
|
+
try {
|
|
54
|
+
const uploaded = await this.options.uploads.resolve(session.workspaceId, attachmentIds);
|
|
55
|
+
const attachments = attachmentIds.length === 0
|
|
56
|
+
? inlineAttachments
|
|
57
|
+
: uploaded.map((attachment) => attachment.attachment);
|
|
58
|
+
const imageAttachments = attachments.filter(isImageAttachment);
|
|
59
|
+
const runnerImageAttachments = attachments.filter(isRunnerImageAttachment);
|
|
60
|
+
const attachmentPaths = await this.persistAttachments(session, runId, attachments);
|
|
61
|
+
const runnerInput = withNonImageAttachmentPrompt(content, attachmentPaths);
|
|
62
|
+
const planActive = this.options.commands
|
|
63
|
+
.list(session.id)
|
|
64
|
+
.some((state) => state.commandId === 'plan');
|
|
65
|
+
const runner = this.options.runners.require(session.runner);
|
|
66
|
+
const active = this.options.runtime
|
|
67
|
+
.listRunsForSession(session.id)
|
|
68
|
+
.find((run) => run.status === 'STARTING' || run.status === 'RUNNING');
|
|
69
|
+
const replaceCurrentRun = intent === 'REPLACE_CURRENT' ? active : undefined;
|
|
70
|
+
const queueWasPaused = this.options.runtime.sessionQueue(session.id).paused;
|
|
71
|
+
const queuedBeforeCreate = this.options.runtime.hasActiveSessionLease(session.id) || intent === 'QUEUE';
|
|
72
|
+
const created = this.options.queue.enqueue({
|
|
73
|
+
runId,
|
|
74
|
+
sessionId: session.id,
|
|
75
|
+
workspaceId: session.workspaceId,
|
|
76
|
+
runner: session.runner,
|
|
77
|
+
input: runner.prepareMessageInput(runnerInput, planActive ? 'plan' : 'default'),
|
|
78
|
+
attachments: runnerImageAttachments,
|
|
79
|
+
attachmentPaths,
|
|
80
|
+
cwd: session.cwd,
|
|
81
|
+
externalSessionId: session.externalSessionId,
|
|
82
|
+
model: session.model,
|
|
83
|
+
effort: session.effort,
|
|
84
|
+
access: session.access,
|
|
85
|
+
collaborationMode: planActive ? 'plan' : 'default',
|
|
86
|
+
secretEnvironment,
|
|
87
|
+
...(runner.serviceTier() === 'fast' ? { serviceTier: 'fast' } : {})
|
|
88
|
+
}, {
|
|
89
|
+
runId,
|
|
90
|
+
sessionId: session.id,
|
|
91
|
+
clientMessageId,
|
|
92
|
+
content: runnerInput,
|
|
93
|
+
...(Number.isSafeInteger(input.__requestUserId)
|
|
94
|
+
? { initiatedByUserId: input.__requestUserId }
|
|
95
|
+
: {}),
|
|
96
|
+
...(attachments.length === 0 ? {} : { attachments })
|
|
97
|
+
});
|
|
98
|
+
enqueued = created.created;
|
|
99
|
+
if (!created.created) {
|
|
100
|
+
this.options.attachments.cleanup(runId);
|
|
101
|
+
return {
|
|
102
|
+
session: this.options.present(session),
|
|
103
|
+
run: created.run,
|
|
104
|
+
delivery: this.options.runtime.isQueuedRun(created.run.id) ? 'QUEUED' : 'STARTED',
|
|
105
|
+
idempotent: true
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const acceptedSession = this.options.runtime.setTemporaryTitleIfMissing(session.id, sessionTitleFromFirstMessage(content));
|
|
109
|
+
this.options.emitSession(acceptedSession);
|
|
110
|
+
this.options.attachments.cache(created.run.id, imageAttachments);
|
|
111
|
+
void this.options.uploads.consume(attachmentIds).catch(() => undefined);
|
|
112
|
+
if (queueWasPaused && active === undefined && intent !== 'QUEUE')
|
|
113
|
+
this.options.runtime.prioritizeQueuedRun(created.run.id);
|
|
114
|
+
if (!queueWasPaused || intent !== 'QUEUE')
|
|
115
|
+
this.options.runtime.resumeSessionQueue(session.id);
|
|
116
|
+
if (replaceCurrentRun !== undefined)
|
|
117
|
+
this.options.queue.replace(created.run.id, replaceCurrentRun.id);
|
|
118
|
+
else if (intent !== 'QUEUE' ||
|
|
119
|
+
!queuedBeforeCreate ||
|
|
120
|
+
!this.options.runtime.hasActiveSessionLease(session.id))
|
|
121
|
+
this.options.queue.schedule(session.id);
|
|
122
|
+
this.options.emitQueue(session.workspaceId, session.id);
|
|
123
|
+
return {
|
|
124
|
+
session: this.options.present(acceptedSession),
|
|
125
|
+
run: created.run,
|
|
126
|
+
delivery: replaceCurrentRun !== undefined
|
|
127
|
+
? 'RESTARTING'
|
|
128
|
+
: queuedBeforeCreate
|
|
129
|
+
? 'QUEUED'
|
|
130
|
+
: 'STARTED',
|
|
131
|
+
idempotent: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (!enqueued) {
|
|
136
|
+
this.options.attachments.clear(runId);
|
|
137
|
+
this.options.attachments.cleanup(runId);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
142
|
+
this.pendingMessages.set(pendingKey, operation);
|
|
70
143
|
try {
|
|
71
|
-
|
|
72
|
-
await this.options.uploads.consume(attachmentIds);
|
|
144
|
+
return await operation;
|
|
73
145
|
}
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
this.options.attachments.cleanup(created.run.id);
|
|
78
|
-
throw error;
|
|
146
|
+
finally {
|
|
147
|
+
if (this.pendingMessages.get(pendingKey) === operation)
|
|
148
|
+
this.pendingMessages.delete(pendingKey);
|
|
79
149
|
}
|
|
80
|
-
const runnerInput = withNonImageAttachmentPrompt(input.content, attachmentPaths);
|
|
81
|
-
this.options.runtime.updateQueuedMessage(created.run.id, runnerInput);
|
|
82
|
-
const planActive = this.options.commands
|
|
83
|
-
.list(session.id)
|
|
84
|
-
.some((state) => state.commandId === 'plan');
|
|
85
|
-
this.options.queue.remember({
|
|
86
|
-
runId: created.run.id,
|
|
87
|
-
sessionId: created.run.sessionId,
|
|
88
|
-
workspaceId: created.run.workspaceId,
|
|
89
|
-
runner: created.run.runner,
|
|
90
|
-
input: this.options.runners
|
|
91
|
-
.require(created.run.runner)
|
|
92
|
-
.prepareMessageInput(runnerInput, planActive ? 'plan' : 'default'),
|
|
93
|
-
attachments: runnerImageAttachments,
|
|
94
|
-
attachmentPaths,
|
|
95
|
-
cwd: session.cwd,
|
|
96
|
-
externalSessionId: session.externalSessionId,
|
|
97
|
-
model: session.model,
|
|
98
|
-
effort: session.effort,
|
|
99
|
-
access: session.access,
|
|
100
|
-
collaborationMode: planActive ? 'plan' : 'default',
|
|
101
|
-
secretEnvironment,
|
|
102
|
-
...(this.options.runners.require(created.run.runner).serviceTier() === 'fast'
|
|
103
|
-
? { serviceTier: 'fast' }
|
|
104
|
-
: {})
|
|
105
|
-
});
|
|
106
|
-
if (queueWasPaused && active === undefined && intent !== 'QUEUE')
|
|
107
|
-
this.options.runtime.prioritizeQueuedRun(created.run.id);
|
|
108
|
-
if (!queueWasPaused || intent !== 'QUEUE')
|
|
109
|
-
this.options.runtime.resumeSessionQueue(session.id);
|
|
110
|
-
this.options.emitQueue(session.workspaceId, session.id);
|
|
111
|
-
if (replaceCurrentRun !== undefined)
|
|
112
|
-
this.options.queue.replace(created.run.id, replaceCurrentRun.id);
|
|
113
|
-
else if (intent !== 'QUEUE' || !queuedBeforeCreate)
|
|
114
|
-
this.options.queue.schedule(session.id);
|
|
115
|
-
return {
|
|
116
|
-
session: this.options.present(session),
|
|
117
|
-
run: created.run,
|
|
118
|
-
delivery: replaceCurrentRun !== undefined ? 'RESTARTING' : queuedBeforeCreate ? 'QUEUED' : 'STARTED',
|
|
119
|
-
idempotent: false
|
|
120
|
-
};
|
|
121
150
|
}
|
|
122
151
|
async persistAttachments(session, runId, attachments) {
|
|
123
152
|
return this.options.attachments.persistFiles(session.cwd, runId, attachments.filter((attachment) => !isRunnerImageAttachment(attachment)));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
2
2
|
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
3
3
|
import type { RunnerName } from '@myagentroam/protocol';
|
|
4
|
-
import type { NodeMcpInstallation } from '../database.js';
|
|
4
|
+
import type { NodeAgentRun, NodeMcpInstallation, NodeMessageAttachmentInput } from '../database.js';
|
|
5
5
|
export interface QueuedRunStart<TAttachment = unknown> {
|
|
6
6
|
readonly runId: string;
|
|
7
7
|
readonly sessionId: string;
|
|
@@ -36,7 +36,17 @@ export declare class WorkspaceQueueWorkbenchService<TAttachment> {
|
|
|
36
36
|
private readonly options;
|
|
37
37
|
private readonly starts;
|
|
38
38
|
constructor(options: WorkspaceQueueWorkbenchServiceOptions);
|
|
39
|
-
|
|
39
|
+
enqueue(start: QueuedRunStart<TAttachment>, message: {
|
|
40
|
+
readonly runId: string;
|
|
41
|
+
readonly sessionId: string;
|
|
42
|
+
readonly clientMessageId: string;
|
|
43
|
+
readonly content: string;
|
|
44
|
+
readonly initiatedByUserId?: number;
|
|
45
|
+
readonly attachments?: readonly NodeMessageAttachmentInput[];
|
|
46
|
+
}): {
|
|
47
|
+
readonly run: NodeAgentRun;
|
|
48
|
+
readonly created: boolean;
|
|
49
|
+
};
|
|
40
50
|
pending(runId: string): QueuedRunStart<TAttachment> | undefined;
|
|
41
51
|
remove(runId: string): void;
|
|
42
52
|
clear(): void;
|
|
@@ -8,8 +8,13 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
8
8
|
constructor(options) {
|
|
9
9
|
this.options = options;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
enqueue(start, message) {
|
|
12
|
+
if (message.runId !== start.runId || message.sessionId !== start.sessionId)
|
|
13
|
+
throw new Error('QUEUE_RUN_MISMATCH');
|
|
14
|
+
const created = this.options.runtime.createMessageRun(message);
|
|
15
|
+
if (created.created)
|
|
16
|
+
this.starts.set(start.runId, start);
|
|
17
|
+
return created;
|
|
13
18
|
}
|
|
14
19
|
pending(runId) {
|
|
15
20
|
return this.starts.get(runId);
|
|
@@ -15,6 +15,7 @@ export interface ConversationHistoryPage {
|
|
|
15
15
|
readonly source: ConversationHistorySource;
|
|
16
16
|
readonly readAt: number;
|
|
17
17
|
readonly latestVisibleAt: number;
|
|
18
|
+
readonly deferredOlderHistory?: boolean;
|
|
18
19
|
}
|
|
19
20
|
export declare function extractId(result: unknown, key: 'thread' | 'turn'): string;
|
|
20
21
|
export declare function settleWithin<T>(promise: Promise<T>, milliseconds: number): Promise<T | undefined>;
|
|
@@ -50,7 +51,7 @@ export declare function nativeConversationPage(session: NodeAgentSession, histor
|
|
|
50
51
|
*/
|
|
51
52
|
export declare function nativeUserClientMessageId(entry: Extract<NativeSessionHistory['items'][number], {
|
|
52
53
|
readonly kind: 'message';
|
|
53
|
-
}>, createdAt: number, runtimeTurns: readonly NodeConversationTurn[]): string | null;
|
|
54
|
+
}>, createdAt: number | null, runtimeTurns: readonly NodeConversationTurn[]): string | null;
|
|
54
55
|
/**
|
|
55
56
|
* Normalize the documented App Server `thread/read(includeTurns: true)`
|
|
56
57
|
* result. Only items with a user-visible Workbench representation are kept;
|
|
@@ -165,14 +165,18 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
165
165
|
.slice(start, end)
|
|
166
166
|
.map((entries, relativeIndex) => {
|
|
167
167
|
const firstIndex = entries[0]?.index ?? 0;
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
? runtimeTurns.find((turn) => runtimeTurnClientMessageId(turn) === clientMessageId)
|
|
168
|
+
const nativeUser = entries.find(({ entry }) => entry.kind === 'message' && entry.role === 'USER')?.entry;
|
|
169
|
+
const matchedClientMessageId = nativeUser?.kind === 'message'
|
|
170
|
+
? nativeUserClientMessageId(nativeUser, nativeUser.createdAt, runtimeTurns)
|
|
171
|
+
: null;
|
|
172
|
+
const runtimeTurn = typeof matchedClientMessageId === 'string'
|
|
173
|
+
? runtimeTurns.find((turn) => runtimeTurnClientMessageId(turn) === matchedClientMessageId)
|
|
175
174
|
: undefined;
|
|
175
|
+
const turnId = runtimeTurn?.id ?? `${session.id}:native:${firstIndex}`;
|
|
176
|
+
const nativeItems = entries.map(({ entry, index }) => ({
|
|
177
|
+
...nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages),
|
|
178
|
+
runId: runtimeTurn?.runId ?? null
|
|
179
|
+
}));
|
|
176
180
|
const runtimeErrors = (runtimeTurn?.items ?? [])
|
|
177
181
|
.filter((item) => item.kind === 'error')
|
|
178
182
|
.map((item) => ({ ...item, turnId, runId: runtimeTurn?.runId ?? null }));
|
|
@@ -180,7 +184,7 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
180
184
|
...nativeItems,
|
|
181
185
|
...runtimeErrors.filter((error) => !nativeItems.some((item) => item.id === error.id))
|
|
182
186
|
];
|
|
183
|
-
const startedAt = items[0]?.startedAt ??
|
|
187
|
+
const startedAt = items[0]?.startedAt ?? null;
|
|
184
188
|
const completedAt = items.at(-1)?.completedAt ?? startedAt;
|
|
185
189
|
const after = lastNativeItemId(entries);
|
|
186
190
|
return {
|
|
@@ -205,7 +209,7 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
205
209
|
completedAt
|
|
206
210
|
};
|
|
207
211
|
});
|
|
208
|
-
const combined = [...nativePageTurns, ...missingRuntimeErrorTurns]
|
|
212
|
+
const combined = [...nativePageTurns, ...missingRuntimeErrorTurns];
|
|
209
213
|
const dropped = combined.slice(0, Math.max(0, combined.length - limit));
|
|
210
214
|
const turns = combined.slice(-limit);
|
|
211
215
|
const nativePageIds = new Set(nativePageTurns.map((turn) => turn.id));
|
|
@@ -241,7 +245,7 @@ function nativeTranscriptTurns(items) {
|
|
|
241
245
|
return turns;
|
|
242
246
|
}
|
|
243
247
|
function nativeTranscriptConversationItem(session, entry, index, turnId, runtimeTurns, registerImages) {
|
|
244
|
-
const time = entry.createdAt
|
|
248
|
+
const time = entry.createdAt;
|
|
245
249
|
const isToolCall = entry.kind === 'tool_call';
|
|
246
250
|
const isUnknown = entry.kind === 'unknown';
|
|
247
251
|
const isReasoning = entry.kind === 'reasoning_summary';
|
|
@@ -336,6 +340,7 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
336
340
|
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
337
341
|
*/
|
|
338
342
|
export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
343
|
+
const candidates = [];
|
|
339
344
|
for (const turn of runtimeTurns) {
|
|
340
345
|
const user = turn.items.find((item) => item.kind === 'user_message');
|
|
341
346
|
if (user === undefined || !isPlainRecord(user.payload))
|
|
@@ -346,13 +351,19 @@ export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
|
346
351
|
if (clientMessageId === null || payload.text !== stripClaudePlanTag(entry.text))
|
|
347
352
|
continue;
|
|
348
353
|
const runtimeCreatedAt = user.startedAt ?? turn.startedAt;
|
|
349
|
-
|
|
350
|
-
runtimeCreatedAt !== undefined &&
|
|
351
|
-
Math.abs(createdAt - runtimeCreatedAt) > 60_000)
|
|
352
|
-
continue;
|
|
353
|
-
return clientMessageId;
|
|
354
|
+
candidates.push({ clientMessageId, createdAt: runtimeCreatedAt ?? null });
|
|
354
355
|
}
|
|
355
|
-
|
|
356
|
+
if (createdAt === null)
|
|
357
|
+
return candidates.length === 1 ? candidates[0].clientMessageId : null;
|
|
358
|
+
const timed = candidates
|
|
359
|
+
.filter((candidate) => candidate.createdAt !== null)
|
|
360
|
+
.map((candidate) => ({
|
|
361
|
+
...candidate,
|
|
362
|
+
distance: Math.abs(createdAt - candidate.createdAt)
|
|
363
|
+
}))
|
|
364
|
+
.filter((candidate) => candidate.distance <= 60_000)
|
|
365
|
+
.sort((left, right) => left.distance - right.distance);
|
|
366
|
+
return timed[0]?.clientMessageId ?? null;
|
|
356
367
|
}
|
|
357
368
|
/**
|
|
358
369
|
* Normalize the documented App Server `thread/read(includeTurns: true)`
|
package/dist/workspace.js
CHANGED
|
@@ -1232,14 +1232,7 @@ export async function readCurrentChanges(workspacePath) {
|
|
|
1232
1232
|
export async function readCurrentChangeDiff(workspacePath, requestedPath) {
|
|
1233
1233
|
const path = safeGitWorkspacePath(requestedPath);
|
|
1234
1234
|
await assertCurrentChangePathAccessible(workspacePath, path);
|
|
1235
|
-
const result = await
|
|
1236
|
-
'diff',
|
|
1237
|
-
'--no-ext-diff',
|
|
1238
|
-
'--no-textconv',
|
|
1239
|
-
'HEAD',
|
|
1240
|
-
'--',
|
|
1241
|
-
`:(literal)${path}`
|
|
1242
|
-
]);
|
|
1235
|
+
const result = await readCurrentChangeGitDiff(workspacePath, path);
|
|
1243
1236
|
const after = await readWorkingTreeText(workspacePath, path);
|
|
1244
1237
|
const binary = result.output.includes(0) || result.text.includes('Binary files ') || after?.binary === true;
|
|
1245
1238
|
if (binary) {
|
|
@@ -1263,6 +1256,38 @@ export async function readCurrentChangeDiff(workspacePath, requestedPath) {
|
|
|
1263
1256
|
...(after === undefined ? {} : { afterTruncated: after.truncated })
|
|
1264
1257
|
};
|
|
1265
1258
|
}
|
|
1259
|
+
async function readCurrentChangeGitDiff(workspacePath, path) {
|
|
1260
|
+
const untracked = await runGit(workspacePath, [
|
|
1261
|
+
'ls-files',
|
|
1262
|
+
'--others',
|
|
1263
|
+
'--exclude-standard',
|
|
1264
|
+
'-z',
|
|
1265
|
+
'--',
|
|
1266
|
+
`:(literal)${path}`
|
|
1267
|
+
]);
|
|
1268
|
+
if (untracked.text.split('\0').includes(path)) {
|
|
1269
|
+
const result = await runGitAllowFailure(workspacePath, [
|
|
1270
|
+
'diff',
|
|
1271
|
+
'--no-index',
|
|
1272
|
+
'--no-ext-diff',
|
|
1273
|
+
'--no-textconv',
|
|
1274
|
+
'--',
|
|
1275
|
+
'/dev/null',
|
|
1276
|
+
path
|
|
1277
|
+
]);
|
|
1278
|
+
if (result.code !== 0 && result.code !== 1)
|
|
1279
|
+
throw new GitCommandError(result.code, result.stderr);
|
|
1280
|
+
return result;
|
|
1281
|
+
}
|
|
1282
|
+
return runGit(workspacePath, [
|
|
1283
|
+
'diff',
|
|
1284
|
+
'--no-ext-diff',
|
|
1285
|
+
'--no-textconv',
|
|
1286
|
+
'HEAD',
|
|
1287
|
+
'--',
|
|
1288
|
+
`:(literal)${path}`
|
|
1289
|
+
]);
|
|
1290
|
+
}
|
|
1266
1291
|
/** Current Changes must not expose an external link. */
|
|
1267
1292
|
export async function isWorkspaceChangeVisible(workspacePath, requestedPath) {
|
|
1268
1293
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-pty": "1.1.0",
|
|
25
25
|
"ws": "^8.21.3",
|
|
26
26
|
"zod": "4.4.3",
|
|
27
|
-
"@myagentroam/protocol": "^0.9.
|
|
27
|
+
"@myagentroam/protocol": "^0.9.4"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/ws": "^8.18.1"
|