@myagentroam/node 0.10.7 → 0.10.8
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.js +60 -5
- package/dist/database.js +3 -1
- package/dist/migrations/v017.js +46 -0
- package/dist/native-session-history.js +7 -15
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +15 -3
- package/dist/runner/claude/tool-normalizer.js +100 -6
- package/dist/runner/codex/conversation-parser.js +41 -30
- package/dist/runner/codex/managed-run-controller.js +12 -1
- package/dist/runner/codex/tool-normalizer.js +5 -2
- package/dist/runner/mar-agent/compact-extensions.js +1 -0
- package/dist/runner/mar-agent/conversation-normalizer.js +43 -78
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +27 -0
- package/dist/runner/mar-agent/managed-run-controller.js +13 -1
- package/dist/runner/mar-agent-runner.js +15 -0
- package/dist/runner/opencode/conversation-parser.js +49 -0
- package/dist/runner/opencode/managed-run-controller.js +12 -0
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/hosted-conversation-node-service.js +29 -2
- package/dist/service/model-usage-outbox.js +167 -0
- package/dist/service/model-usage-service.js +61 -0
- package/dist/service/node-control-message-service.js +7 -2
- package/dist/service/node-request-service.js +3 -1
- package/dist/service/node-run-event-bridge.js +6 -0
- package/dist/service/runner-usage-tracker.js +62 -0
- package/dist/service/session-command-service.js +4 -0
- package/dist/service/session-message-service.js +6 -0
- package/dist/service/session-query-service.js +16 -0
- package/dist/util/node-operation-parsers.js +0 -21
- package/dist/util/runner-native-session-parsers.js +9 -4
- package/package.json +5 -5
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { MODEL_USAGE_BATCH_BYTES, MODEL_USAGE_BATCH_LIMIT, MODEL_USAGE_OUTBOX_RETENTION_MS, modelUsageAckSchema, modelUsageGrantSchema, modelUsageRecordSchema } from '@myagentroam/protocol';
|
|
2
|
+
const MAX_BYTES = 32 * 1024 * 1024;
|
|
3
|
+
/** Durable, bounded, at-least-once delivery independent of browser subscriptions. */
|
|
4
|
+
export class ModelUsageOutbox {
|
|
5
|
+
options;
|
|
6
|
+
enabled = false;
|
|
7
|
+
timer;
|
|
8
|
+
inFlight;
|
|
9
|
+
attempts = 0;
|
|
10
|
+
now;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.now = options.now ?? Date.now;
|
|
14
|
+
}
|
|
15
|
+
connected(payload) {
|
|
16
|
+
this.disconnected();
|
|
17
|
+
const capability = payload?.modelUsage;
|
|
18
|
+
this.enabled = capability?.apiVersion === 1;
|
|
19
|
+
if (this.enabled) {
|
|
20
|
+
this.recover();
|
|
21
|
+
this.schedule(0);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
disconnected() {
|
|
25
|
+
this.enabled = false;
|
|
26
|
+
if (this.timer)
|
|
27
|
+
clearTimeout(this.timer);
|
|
28
|
+
this.timer = undefined;
|
|
29
|
+
this.inFlight = undefined;
|
|
30
|
+
this.attempts = 0;
|
|
31
|
+
}
|
|
32
|
+
record(grant, value) {
|
|
33
|
+
try {
|
|
34
|
+
const database = this.options.database();
|
|
35
|
+
const namespace = this.options.namespace();
|
|
36
|
+
if (!database || !namespace)
|
|
37
|
+
return;
|
|
38
|
+
const record = modelUsageRecordSchema.parse(value);
|
|
39
|
+
const parsedGrant = modelUsageGrantSchema.parse(grant);
|
|
40
|
+
const payload = JSON.stringify({ grant: parsedGrant, record });
|
|
41
|
+
const bytes = Buffer.byteLength(payload);
|
|
42
|
+
this.expire(database);
|
|
43
|
+
const current = database
|
|
44
|
+
.prepare('SELECT revision,bytes FROM model_usage_outbox WHERE namespace=? AND record_id=?')
|
|
45
|
+
.get(namespace, record.recordId);
|
|
46
|
+
if (current && Number(current.revision) >= record.revision)
|
|
47
|
+
return;
|
|
48
|
+
const total = Number(database.prepare('SELECT COALESCE(SUM(bytes),0) AS bytes FROM model_usage_outbox').get()
|
|
49
|
+
?.bytes ?? 0);
|
|
50
|
+
if (bytes > MODEL_USAGE_BATCH_BYTES / 2 ||
|
|
51
|
+
total - Number(current?.bytes ?? 0) + bytes > MAX_BYTES) {
|
|
52
|
+
this.options.diagnose('USAGE_OUTBOX_FULL');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
database
|
|
56
|
+
.prepare(`INSERT INTO model_usage_outbox(namespace,record_id,revision,payload,bytes,updated_at,ended_at) VALUES (?,?,?,?,?,?,?)
|
|
57
|
+
ON CONFLICT(namespace,record_id) DO UPDATE SET revision=excluded.revision,payload=excluded.payload,bytes=excluded.bytes,updated_at=excluded.updated_at,ended_at=excluded.ended_at`)
|
|
58
|
+
.run(namespace, record.recordId, record.revision, payload, bytes, this.now(), record.endedAt ?? null);
|
|
59
|
+
if (this.enabled && !this.inFlight && !this.timer)
|
|
60
|
+
this.schedule(250);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
this.options.diagnose('USAGE_OUTBOX_WRITE_FAILED');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
handle(envelope) {
|
|
67
|
+
if (envelope.type !== 'usage.ack')
|
|
68
|
+
return false;
|
|
69
|
+
const parsed = modelUsageAckSchema.safeParse(envelope.payload);
|
|
70
|
+
const namespace = this.options.namespace();
|
|
71
|
+
const database = this.options.database();
|
|
72
|
+
if (!parsed.success || !this.inFlight || !namespace || !database)
|
|
73
|
+
return true;
|
|
74
|
+
try {
|
|
75
|
+
for (const result of parsed.data.results) {
|
|
76
|
+
if (this.inFlight.get(result.recordId) !== result.revision)
|
|
77
|
+
continue;
|
|
78
|
+
database
|
|
79
|
+
.prepare('DELETE FROM model_usage_outbox WHERE namespace=? AND record_id=? AND revision=?')
|
|
80
|
+
.run(namespace, result.recordId, result.revision);
|
|
81
|
+
if (result.status === 'rejected')
|
|
82
|
+
this.options.diagnose(result.code ?? 'USAGE_REJECTED');
|
|
83
|
+
}
|
|
84
|
+
this.inFlight = undefined;
|
|
85
|
+
this.attempts = 0;
|
|
86
|
+
this.schedule(250);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
this.options.diagnose('USAGE_OUTBOX_ACK_FAILED');
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
schedule(delay) {
|
|
94
|
+
if (this.timer)
|
|
95
|
+
clearTimeout(this.timer);
|
|
96
|
+
this.timer = setTimeout(() => {
|
|
97
|
+
this.timer = undefined;
|
|
98
|
+
this.flush();
|
|
99
|
+
}, delay);
|
|
100
|
+
this.timer.unref();
|
|
101
|
+
}
|
|
102
|
+
flush() {
|
|
103
|
+
const database = this.options.database();
|
|
104
|
+
const namespace = this.options.namespace();
|
|
105
|
+
if (!this.enabled || !database || !namespace)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
this.expire(database);
|
|
109
|
+
const rows = database
|
|
110
|
+
.prepare('SELECT payload FROM model_usage_outbox WHERE namespace=? AND ended_at IS NOT NULL ORDER BY updated_at,record_id LIMIT ?')
|
|
111
|
+
.all(namespace, MODEL_USAGE_BATCH_LIMIT);
|
|
112
|
+
const entries = [];
|
|
113
|
+
let bytes = 100;
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
bytes += Buffer.byteLength(String(row.payload)) + 1;
|
|
116
|
+
if (bytes > MODEL_USAGE_BATCH_BYTES)
|
|
117
|
+
break;
|
|
118
|
+
entries.push(JSON.parse(String(row.payload)));
|
|
119
|
+
}
|
|
120
|
+
if (!entries.length) {
|
|
121
|
+
this.inFlight = undefined;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.inFlight = new Map(entries.map(({ record }) => [record.recordId, record.revision]));
|
|
125
|
+
this.options.send('usage.batch', { apiVersion: 1, entries });
|
|
126
|
+
this.schedule(Math.min(60_000, 1000 * 2 ** Math.min(this.attempts++, 6)));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
this.options.diagnose('USAGE_OUTBOX_SEND_FAILED');
|
|
130
|
+
this.schedule(30_000);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
recover() {
|
|
134
|
+
const database = this.options.database();
|
|
135
|
+
const namespace = this.options.namespace();
|
|
136
|
+
if (!database || !namespace)
|
|
137
|
+
return;
|
|
138
|
+
// Reconnect must not finalize requests that are still running.
|
|
139
|
+
this.expire(database);
|
|
140
|
+
}
|
|
141
|
+
recoverInterrupted() {
|
|
142
|
+
const database = this.options.database();
|
|
143
|
+
const namespace = this.options.namespace();
|
|
144
|
+
if (!database || !namespace)
|
|
145
|
+
return;
|
|
146
|
+
this.expire(database);
|
|
147
|
+
const rows = database
|
|
148
|
+
.prepare('SELECT payload FROM model_usage_outbox WHERE namespace=? AND ended_at IS NULL')
|
|
149
|
+
.all(namespace);
|
|
150
|
+
for (const row of rows) {
|
|
151
|
+
const entry = JSON.parse(String(row.payload));
|
|
152
|
+
this.record(entry.grant, {
|
|
153
|
+
...entry.record,
|
|
154
|
+
revision: entry.record.revision + 1,
|
|
155
|
+
state: 'interrupted',
|
|
156
|
+
endedAt: Math.max(entry.record.startedAt, this.now())
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
expire(database) {
|
|
161
|
+
const result = database
|
|
162
|
+
.prepare('DELETE FROM model_usage_outbox WHERE updated_at<?')
|
|
163
|
+
.run(this.now() - MODEL_USAGE_OUTBOX_RETENTION_MS);
|
|
164
|
+
if (result.changes)
|
|
165
|
+
this.options.diagnose('USAGE_OUTBOX_EXPIRED');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { modelUsageGrantSchema, modelUsageGrantClaimsSchema, modelUsageRecordSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { nodeLog } from '../operational.js';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
/** Execution-private attribution never enters Run projections or browser Watch events. */
|
|
5
|
+
export class NodeModelUsageService {
|
|
6
|
+
outbox;
|
|
7
|
+
nodeId;
|
|
8
|
+
scope = new AsyncLocalStorage();
|
|
9
|
+
bindings = new Map();
|
|
10
|
+
constructor(outbox, nodeId) {
|
|
11
|
+
this.outbox = outbox;
|
|
12
|
+
this.nodeId = nodeId;
|
|
13
|
+
}
|
|
14
|
+
withGrant(grant, action) {
|
|
15
|
+
return this.scope.run(grant, action);
|
|
16
|
+
}
|
|
17
|
+
bindInherited(runId, sessionId, runner) {
|
|
18
|
+
if (!this.bindings.has(runId))
|
|
19
|
+
this.bind(runId, this.scope.getStore(), sessionId, runner);
|
|
20
|
+
}
|
|
21
|
+
bind(runId, grant, sessionId, runner) {
|
|
22
|
+
this.bindings.delete(runId);
|
|
23
|
+
if (grant === undefined)
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
const parsedGrant = modelUsageGrantSchema.parse(grant);
|
|
27
|
+
const claims = modelUsageGrantClaimsSchema.parse(JSON.parse(Buffer.from(parsedGrant.token.split('.')[0], 'base64url').toString()));
|
|
28
|
+
if (claims.nodeId !== this.nodeId() || claims.scopeId !== parsedGrant.scopeId)
|
|
29
|
+
return;
|
|
30
|
+
this.bindings.set(runId, { grant: parsedGrant, scopeId: claims.scopeId, sessionId, runner });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
nodeLog('model.usage.invalid', { code: 'USAGE_GRANT_INVALID' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
release(runId) {
|
|
37
|
+
this.bindings.delete(runId);
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this.bindings.clear();
|
|
41
|
+
}
|
|
42
|
+
record(runId, value) {
|
|
43
|
+
const binding = this.bindings.get(runId);
|
|
44
|
+
if (!binding || !value || typeof value !== 'object')
|
|
45
|
+
return;
|
|
46
|
+
const parsed = modelUsageRecordSchema.safeParse({
|
|
47
|
+
...value,
|
|
48
|
+
scopeId: binding.scopeId,
|
|
49
|
+
runner: binding.runner,
|
|
50
|
+
runId,
|
|
51
|
+
sessionId: typeof value.sessionId === 'string'
|
|
52
|
+
? value.sessionId
|
|
53
|
+
: binding.sessionId
|
|
54
|
+
});
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
nodeLog('model.usage.invalid', { code: 'USAGE_RECORD_INVALID' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.outbox()?.record(binding.grant, parsed.data);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -22,7 +22,7 @@ export class NodeControlMessageService {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (envelope.type === 'authenticated') {
|
|
25
|
-
this.options.connected();
|
|
25
|
+
this.options.connected(envelope.payload);
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
if (envelope.type === 'workspace.inspect') {
|
|
@@ -45,6 +45,10 @@ export class NodeControlMessageService {
|
|
|
45
45
|
if (envelope.type.startsWith('terminal.direct.') &&
|
|
46
46
|
this.options.handleTerminalDirect?.(envelope) === true)
|
|
47
47
|
return;
|
|
48
|
+
if (envelope.type === 'usage.ack') {
|
|
49
|
+
this.options.usageAck?.(envelope.payload);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
48
52
|
if (envelope.type === 'ack')
|
|
49
53
|
return;
|
|
50
54
|
if (envelope.type === 'runs.reconcile') {
|
|
@@ -58,6 +62,7 @@ export class NodeControlMessageService {
|
|
|
58
62
|
if (envelope.type === 'run.start') {
|
|
59
63
|
this.options.send('ack', { type: envelope.type }, envelope.id);
|
|
60
64
|
try {
|
|
65
|
+
this.options.bindUsage?.(envelope.payload);
|
|
61
66
|
this.options.runners.startManagedRun(envelope);
|
|
62
67
|
}
|
|
63
68
|
catch (error) {
|
|
@@ -90,7 +95,7 @@ export class NodeControlMessageService {
|
|
|
90
95
|
this.options.close(4002, 'MESSAGE_INVALID');
|
|
91
96
|
return;
|
|
92
97
|
}
|
|
93
|
-
void this.options.register(payload.nodeId, payload.credential);
|
|
98
|
+
void this.options.register(payload.nodeId, payload.credential, envelope.payload);
|
|
94
99
|
}
|
|
95
100
|
decideApproval(envelope) {
|
|
96
101
|
const payload = envelope.payload;
|
|
@@ -19,12 +19,14 @@ export class NodeRequestService {
|
|
|
19
19
|
}
|
|
20
20
|
const startedAt = performance.now();
|
|
21
21
|
try {
|
|
22
|
+
const usageGrant = payload.userAuthorization.usageGrant;
|
|
22
23
|
const data = payload.data !== null && typeof payload.data === 'object'
|
|
23
24
|
? {
|
|
24
25
|
...payload.data,
|
|
25
26
|
__workspaceWatchSessionHash: payload.userAuthorization
|
|
26
27
|
.sessionHash,
|
|
27
|
-
__requestUserId: payload.userAuthorization.userId
|
|
28
|
+
__requestUserId: payload.userAuthorization.userId,
|
|
29
|
+
...(usageGrant === undefined ? {} : { __usageGrant: usageGrant })
|
|
28
30
|
}
|
|
29
31
|
: payload.data;
|
|
30
32
|
const result = await this.options.execute(payload.operation, data);
|
|
@@ -113,6 +113,12 @@ export class NodeRunEventBridge {
|
|
|
113
113
|
this.emitRun(input.runId, 'approval.request', input);
|
|
114
114
|
}
|
|
115
115
|
emitRun(runId, eventType, payload, status) {
|
|
116
|
+
if (eventType === 'run.started')
|
|
117
|
+
this.options.bindModelUsage?.(runId);
|
|
118
|
+
if (eventType === 'run.usage') {
|
|
119
|
+
this.options.modelUsage?.(runId, payload);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
116
122
|
this.options.events.emit(runId, eventType, payload, status);
|
|
117
123
|
}
|
|
118
124
|
emitQueue(workspaceId, sessionId) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function usageToken(value) {
|
|
3
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
|
|
4
|
+
? String(value)
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
export function usageTokenSum(...values) {
|
|
8
|
+
return values.some((value) => value === undefined)
|
|
9
|
+
? undefined
|
|
10
|
+
: values.reduce((sum, value) => sum + BigInt(value), 0n).toString();
|
|
11
|
+
}
|
|
12
|
+
/** Neutral revision tracking; native token semantics remain inside each Runner normalizer. */
|
|
13
|
+
export class RunnerUsageTracker {
|
|
14
|
+
identity;
|
|
15
|
+
publish;
|
|
16
|
+
now;
|
|
17
|
+
records = new Map();
|
|
18
|
+
constructor(identity, publish, now = Date.now) {
|
|
19
|
+
this.identity = identity;
|
|
20
|
+
this.publish = publish;
|
|
21
|
+
this.now = now;
|
|
22
|
+
}
|
|
23
|
+
snapshot(id, values, complete, coverage = 'request') {
|
|
24
|
+
const previous = this.records.get(id);
|
|
25
|
+
const timestamp = this.now();
|
|
26
|
+
const recordId = createHash('sha256')
|
|
27
|
+
.update(JSON.stringify([this.identity, id]))
|
|
28
|
+
.digest('hex');
|
|
29
|
+
const record = {
|
|
30
|
+
...previous,
|
|
31
|
+
...values,
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
recordId,
|
|
34
|
+
requestId: recordId,
|
|
35
|
+
revision: (previous?.revision ?? 0) + 1,
|
|
36
|
+
startedAt: previous?.startedAt ?? timestamp,
|
|
37
|
+
state: complete ? 'succeeded' : (previous?.state ?? 'running'),
|
|
38
|
+
coverage,
|
|
39
|
+
...(complete ? { endedAt: previous?.endedAt ?? timestamp } : {})
|
|
40
|
+
};
|
|
41
|
+
if (previous &&
|
|
42
|
+
JSON.stringify({ ...record, revision: 0 }) === JSON.stringify({ ...previous, revision: 0 }))
|
|
43
|
+
return;
|
|
44
|
+
this.records.set(id, record);
|
|
45
|
+
if (!previous || record.state !== 'running')
|
|
46
|
+
this.publish(record);
|
|
47
|
+
}
|
|
48
|
+
finish(state) {
|
|
49
|
+
for (const [id, previous] of this.records) {
|
|
50
|
+
if (previous.state !== 'running')
|
|
51
|
+
continue;
|
|
52
|
+
const record = {
|
|
53
|
+
...previous,
|
|
54
|
+
revision: previous.revision + 1,
|
|
55
|
+
state,
|
|
56
|
+
endedAt: Math.max(previous.startedAt, this.now())
|
|
57
|
+
};
|
|
58
|
+
this.records.set(id, record);
|
|
59
|
+
this.publish(record);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modelUsageGrantSchema } from '@myagentroam/protocol';
|
|
1
2
|
import { parseCommandInvocation } from '../runner-command-engine.js';
|
|
2
3
|
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
3
4
|
import { parseSecretEnvironment } from '../util/secret-environment.js';
|
|
@@ -66,6 +67,9 @@ export class SessionCommandService {
|
|
|
66
67
|
return this.options.goal({
|
|
67
68
|
session,
|
|
68
69
|
objective: invocation.argument,
|
|
70
|
+
...(input.__usageGrant === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { usageGrant: modelUsageGrantSchema.parse(input.__usageGrant) }),
|
|
69
73
|
...(Number.isSafeInteger(input.__requestUserId)
|
|
70
74
|
? { initiatedByUserId: input.__requestUserId }
|
|
71
75
|
: {}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modelUsageGrantSchema } from '@myagentroam/protocol';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { isImageAttachment, isRunnerImageAttachment, parseComposerAttachmentIds, parseMessageAttachments, sessionTitleFromFirstMessage, withNonImageAttachmentPrompt } from '../util/node-operation-parsers.js';
|
|
3
4
|
import { parseSecretEnvironment } from '../util/secret-environment.js';
|
|
@@ -41,6 +42,7 @@ export class SessionMessageService {
|
|
|
41
42
|
marAgentModel: input.marAgentModel,
|
|
42
43
|
marAgentModels: input.marAgentModels,
|
|
43
44
|
marAgentCompact: input.marAgentCompact,
|
|
45
|
+
__usageGrant: input.usageGrant,
|
|
44
46
|
marAgentRuntimeScopeId: input.marAgentRuntimeScopeId
|
|
45
47
|
});
|
|
46
48
|
}
|
|
@@ -58,6 +60,7 @@ export class SessionMessageService {
|
|
|
58
60
|
marAgentModel: input.marAgentModel,
|
|
59
61
|
marAgentModels: input.marAgentModels,
|
|
60
62
|
marAgentCompact: input.marAgentCompact,
|
|
63
|
+
__usageGrant: input.usageGrant,
|
|
61
64
|
marAgentRuntimeScopeId: input.marAgentRuntimeScopeId
|
|
62
65
|
});
|
|
63
66
|
}
|
|
@@ -188,6 +191,9 @@ export class SessionMessageService {
|
|
|
188
191
|
else
|
|
189
192
|
this.options.runtime.updateQueueItemAttachments(created.queueItem.id, attachments, displayInput);
|
|
190
193
|
this.options.coordinator.prepare(submissionId, {
|
|
194
|
+
...(input.__usageGrant === undefined
|
|
195
|
+
? {}
|
|
196
|
+
: { usageGrant: modelUsageGrantSchema.parse(input.__usageGrant) }),
|
|
191
197
|
runId,
|
|
192
198
|
sessionId: currentSession.id,
|
|
193
199
|
workspaceId: currentSession.workspaceId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isPlainRecord } from '../runner/codex/conversation-parser.js';
|
|
2
|
+
import { toolResultResourceInputSchema, toolResultResourcePageSchema } from '@myagentroam/protocol';
|
|
2
3
|
export class SessionQueryService {
|
|
3
4
|
options;
|
|
4
5
|
constructor(options) {
|
|
@@ -11,6 +12,7 @@ export class SessionQueryService {
|
|
|
11
12
|
'session.turns.list': (data) => this.turns(record(data)),
|
|
12
13
|
'session.events.read': (data) => this.events(record(data)),
|
|
13
14
|
'session.context.read': (data) => this.readContext(record(data)),
|
|
15
|
+
'session.tool-resource.read': (data) => this.readToolResource(record(data)),
|
|
14
16
|
'session.discover': (data) => this.discover(record(data))
|
|
15
17
|
};
|
|
16
18
|
}
|
|
@@ -39,6 +41,20 @@ export class SessionQueryService {
|
|
|
39
41
|
throw new Error('SESSION_NOT_FOUND');
|
|
40
42
|
return this.options.runtime.listConversationItems({ sessionId: session.id, ...page });
|
|
41
43
|
}
|
|
44
|
+
async readToolResource(input) {
|
|
45
|
+
const session = await this.requireSession(input.sessionId);
|
|
46
|
+
if (typeof input.workspaceId !== 'string' || input.workspaceId !== session.workspaceId)
|
|
47
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
48
|
+
const resource = toolResultResourceInputSchema.parse({
|
|
49
|
+
resourceRef: input.resourceRef,
|
|
50
|
+
cursor: input.cursor,
|
|
51
|
+
maxBytes: input.maxBytes
|
|
52
|
+
});
|
|
53
|
+
const runner = this.options.runners.product(session.runner);
|
|
54
|
+
if (!runner)
|
|
55
|
+
throw new Error('TOOL_RESOURCE_UNSUPPORTED');
|
|
56
|
+
return toolResultResourcePageSchema.parse(await runner.readToolResultResource(session, resource));
|
|
57
|
+
}
|
|
42
58
|
async readContext(input) {
|
|
43
59
|
return this.options.context.read(await this.requireSession(input.sessionId));
|
|
44
60
|
}
|
|
@@ -160,27 +160,6 @@ export function extractClaudeToolUses(message) {
|
|
|
160
160
|
];
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
export function extractClaudeToolResults(message) {
|
|
164
|
-
const parentId = typeof message.parent_tool_use_id === 'string' ? message.parent_tool_use_id : null;
|
|
165
|
-
const direct = compactRunnerValue(message.tool_use_result, 10_000);
|
|
166
|
-
if (parentId !== null && direct.text.length > 0)
|
|
167
|
-
return [
|
|
168
|
-
{ toolUseId: parentId, output: direct.text, failed: false, truncated: direct.truncated }
|
|
169
|
-
];
|
|
170
|
-
return claudeContentBlocks(message).flatMap((block) => {
|
|
171
|
-
if (block.type !== 'tool_result' || typeof block.tool_use_id !== 'string')
|
|
172
|
-
return [];
|
|
173
|
-
const result = compactRunnerValue(block.content, 10_000);
|
|
174
|
-
return [
|
|
175
|
-
{
|
|
176
|
-
toolUseId: block.tool_use_id,
|
|
177
|
-
output: result.text,
|
|
178
|
-
failed: block.is_error === true,
|
|
179
|
-
truncated: result.truncated
|
|
180
|
-
}
|
|
181
|
-
];
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
163
|
export function claudeContentBlocks(message) {
|
|
185
164
|
const content = message.message;
|
|
186
165
|
if (!isPlainRecord(content) || !Array.isArray(content.content))
|
|
@@ -4,7 +4,7 @@ import { stripClaudePlanTag } from '../runner-command-engine.js';
|
|
|
4
4
|
import { publicGoalHistoryText } from '../service/goal-runtime.js';
|
|
5
5
|
import { parseCodexComposerImageMarkers } from '../runner/codex/composer-image-markers.js';
|
|
6
6
|
import { normalizeCodexTool } from '../runner/codex/tool-normalizer.js';
|
|
7
|
-
import { normalizeClaudeTool } from '../runner/claude/tool-normalizer.js';
|
|
7
|
+
import { normalizeClaudeTool, restoreClaudeSubagentTitles } from '../runner/claude/tool-normalizer.js';
|
|
8
8
|
import { compactRunnerText, isPlainRecord, nativePageEnd } from './node-operation-parsers.js';
|
|
9
9
|
export function extractId(result, key) {
|
|
10
10
|
if (typeof result === 'object' &&
|
|
@@ -148,6 +148,11 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
148
148
|
const nativeTurnIds = nativeTranscriptTurnIds(session.id, nativeTurns);
|
|
149
149
|
const end = nativePageEnd(effectiveCursor, session.id, nativeTurns.length);
|
|
150
150
|
const start = Math.max(0, end - limit);
|
|
151
|
+
const knownClaudeSubagents = restoreClaudeSubagentTitles(session.runner === 'claude-code'
|
|
152
|
+
? history.items
|
|
153
|
+
.slice(0, nativeTurns[start]?.[0]?.index ?? 0)
|
|
154
|
+
.filter((entry) => entry.kind === 'tool_call')
|
|
155
|
+
: []);
|
|
151
156
|
const nativePageTurns = nativeTurns
|
|
152
157
|
.slice(start, end)
|
|
153
158
|
.map((entries, relativeIndex) => {
|
|
@@ -161,7 +166,7 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
161
166
|
const turnId = runtimeTurn?.id ?? nativeTurnIds[start + relativeIndex];
|
|
162
167
|
const itemIdentities = nativeTranscriptItemIdentities(entries);
|
|
163
168
|
const nativeItems = entries.map(({ entry, index }, entryIndex) => ({
|
|
164
|
-
...nativeTranscriptConversationItem(session, entry, index, itemIdentities[entryIndex], turnId, runtimeTurns, registerImages),
|
|
169
|
+
...nativeTranscriptConversationItem(session, entry, index, itemIdentities[entryIndex], turnId, runtimeTurns, registerImages, knownClaudeSubagents),
|
|
165
170
|
runId: runtimeTurn?.runId ?? null
|
|
166
171
|
}));
|
|
167
172
|
const runtimeErrors = (runtimeTurn?.items ?? [])
|
|
@@ -299,14 +304,14 @@ function nativeTranscriptTurns(items) {
|
|
|
299
304
|
turns.push(current);
|
|
300
305
|
return turns;
|
|
301
306
|
}
|
|
302
|
-
function nativeTranscriptConversationItem(session, entry, index, itemIdentity, turnId, runtimeTurns, registerImages) {
|
|
307
|
+
function nativeTranscriptConversationItem(session, entry, index, itemIdentity, turnId, runtimeTurns, registerImages, knownClaudeSubagents) {
|
|
303
308
|
const time = entry.createdAt;
|
|
304
309
|
const isToolCall = entry.kind === 'tool_call';
|
|
305
310
|
const isUnknown = entry.kind === 'unknown';
|
|
306
311
|
const isReasoning = entry.kind === 'reasoning_summary';
|
|
307
312
|
const isContextCompaction = entry.kind === 'context_compaction';
|
|
308
313
|
const normalizedClaudeTool = session.runner === 'claude-code' && isToolCall
|
|
309
|
-
? normalizeClaudeTool(entry.toolName, entry.input, entry.outputSummary, entry.failed, entry.inputTruncated || entry.outputTruncated)
|
|
314
|
+
? normalizeClaudeTool(entry.toolName, entry.input, entry.outputSummary, entry.failed, entry.inputTruncated || entry.outputTruncated, knownClaudeSubagents, entry.claudeResult)
|
|
310
315
|
: undefined;
|
|
311
316
|
const normalizedCodexTool = session.runner === 'codex' && isToolCall
|
|
312
317
|
? normalizeCodexTool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.8",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"playwright": "1.62.1",
|
|
38
38
|
"ws": "^8.21.3",
|
|
39
39
|
"zod": "4.4.3",
|
|
40
|
-
"@myagentroam/agent": "0.10.
|
|
41
|
-
"@myagentroam/
|
|
42
|
-
"@myagentroam/protocol": "0.10.
|
|
43
|
-
"@myagentroam/
|
|
40
|
+
"@myagentroam/agent": "0.10.8",
|
|
41
|
+
"@myagentroam/marctl": "0.10.2",
|
|
42
|
+
"@myagentroam/protocol": "0.10.8",
|
|
43
|
+
"@myagentroam/code-navigation": "0.10.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/ws": "^8.18.1"
|