@myagentroam/node 0.1.8 → 0.9.2
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/capabilities.js +6 -3
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +8 -1
- package/dist/codex-app-server.d.ts +6 -0
- package/dist/codex-app-server.js +6 -0
- package/dist/connector/node-connector-options.d.ts +1 -0
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +43 -14
- package/dist/database.d.ts +23 -0
- package/dist/database.js +84 -2
- package/dist/migrations/v004.d.ts +4 -0
- package/dist/migrations/v004.js +35 -0
- package/dist/native-session-history.js +72 -14
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +12 -0
- package/dist/runner/abstract-runner.d.ts +13 -3
- package/dist/runner/abstract-runner.js +78 -4
- package/dist/runner/claude/managed-run-controller.js +18 -7
- package/dist/runner/claude-code-runner.d.ts +1 -0
- package/dist/runner/claude-code-runner.js +17 -4
- package/dist/runner/codex/conversation-parser.d.ts +2 -2
- package/dist/runner/codex/conversation-parser.js +30 -12
- package/dist/runner/codex/managed-run-controller.js +11 -8
- package/dist/runner/codex-runner.d.ts +17 -3
- package/dist/runner/codex-runner.js +225 -12
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +15 -15
- package/dist/runner/opencode/managed-run-controller.d.ts +2 -1
- package/dist/runner/opencode/managed-run-controller.js +36 -12
- package/dist/runner/opencode-runner.d.ts +2 -1
- package/dist/runner/opencode-runner.js +23 -3
- package/dist/runner/runner-registry.d.ts +1 -1
- package/dist/runner/runner-registry.js +2 -2
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -41
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-command-detector.d.ts +3 -0
- package/dist/runtime-command-detector.js +78 -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 +155 -0
- package/dist/service/mcp-installation-verifier.d.ts +9 -0
- package/dist/service/mcp-installation-verifier.js +87 -0
- package/dist/service/mcp-node-operation-service.d.ts +11 -0
- package/dist/service/mcp-node-operation-service.js +90 -0
- package/dist/service/mcp-package-installer.d.ts +8 -0
- package/dist/service/mcp-package-installer.js +136 -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-request-service.js +2 -1
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +21 -11
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +8 -2
- package/dist/service/runner-service.d.ts +2 -0
- package/dist/service/runner-service.js +5 -2
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-lifecycle-service.js +7 -4
- 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.js +3 -26
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.js +2 -1
- package/dist/service/workbench-manifest-service.d.ts +2 -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 +2 -0
- package/dist/service/workspace-queue-workbench-service.js +5 -3
- package/dist/service/workspace-service.js +2 -0
- 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-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +5 -14
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -30,6 +30,7 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
30
30
|
protected profileForValidation(): RunnerProfile;
|
|
31
31
|
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
|
|
32
32
|
defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
|
|
33
|
+
mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
|
|
33
34
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
34
35
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
35
36
|
readExternalActivity(session: NodeAgentSession): Promise<"UNAVAILABLE" | "EXTERNAL_ACTIVE" | "IDLE">;
|
|
@@ -55,6 +56,6 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
55
56
|
maxTokens: number;
|
|
56
57
|
} | undefined>;
|
|
57
58
|
stop(): Promise<void>;
|
|
58
|
-
managedClient(environment: Readonly<Record<string, string
|
|
59
|
+
managedClient(environment: Readonly<Record<string, string>>, isolationKey?: string): OpenCodeServerClient;
|
|
59
60
|
releaseManagedClient(client: OpenCodeServerClient): void;
|
|
60
61
|
}
|
|
@@ -87,6 +87,24 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
87
87
|
access: 'default'
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
+
mcpConfiguration(raw, secrets) {
|
|
91
|
+
const servers = super.mcpConfiguration(raw, secrets);
|
|
92
|
+
return Object.fromEntries(servers.map((server) => [
|
|
93
|
+
server.name,
|
|
94
|
+
server.transport === 'STDIO'
|
|
95
|
+
? {
|
|
96
|
+
type: 'local',
|
|
97
|
+
command: [server.command, ...(server.args ?? [])],
|
|
98
|
+
environment: { ...(server.environment ?? {}) }
|
|
99
|
+
}
|
|
100
|
+
: {
|
|
101
|
+
type: 'remote',
|
|
102
|
+
url: server.url,
|
|
103
|
+
headers: { ...(server.headers ?? {}) },
|
|
104
|
+
oauth: false
|
|
105
|
+
}
|
|
106
|
+
]));
|
|
107
|
+
}
|
|
90
108
|
async discoverSessions(context, workspace) {
|
|
91
109
|
const result = [];
|
|
92
110
|
for (const target of workspace === undefined ? context.workspaces : [workspace]) {
|
|
@@ -270,11 +288,13 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
270
288
|
await Promise.allSettled(stopping);
|
|
271
289
|
this.stoppingClients.clear();
|
|
272
290
|
}
|
|
273
|
-
managedClient(environment) {
|
|
291
|
+
managedClient(environment, isolationKey) {
|
|
274
292
|
const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
|
|
275
|
-
if (entries.length === 0)
|
|
293
|
+
if (entries.length === 0 && isolationKey === undefined)
|
|
276
294
|
return this.client;
|
|
277
|
-
const fingerprint = createHash('sha256')
|
|
295
|
+
const fingerprint = createHash('sha256')
|
|
296
|
+
.update(JSON.stringify({ entries, isolationKey }))
|
|
297
|
+
.digest('hex');
|
|
278
298
|
const existing = this.environmentClients.get(fingerprint);
|
|
279
299
|
if (existing !== undefined) {
|
|
280
300
|
existing.leases += 1;
|
|
@@ -10,7 +10,7 @@ export declare class RunnerRegistry {
|
|
|
10
10
|
product(name: unknown): AbstractRunner<RunnerName> | undefined;
|
|
11
11
|
profiles(capabilities: NodeCapabilities): readonly RunnerProfile[];
|
|
12
12
|
refreshProfiles(capabilities: NodeCapabilities, workspace?: import('../database.js').NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<readonly RunnerProfile[]>;
|
|
13
|
-
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: import('../database.js').NodeWorkspace): boolean;
|
|
13
|
+
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: import('../database.js').NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
|
|
14
14
|
startManagedRun(envelope: NodeEnvelope): boolean;
|
|
15
15
|
cancelManagedRun(runId: string, intent: RunnerCancellationIntent): boolean;
|
|
16
16
|
decideApproval(input: RunnerApprovalDecision): boolean;
|
|
@@ -38,8 +38,8 @@ export class RunnerRegistry {
|
|
|
38
38
|
const profiles = await Promise.all(this.advertised().map((runner) => runner.refreshProfile(capabilities, workspace, environment)));
|
|
39
39
|
return profiles.flatMap((profile) => (profile === undefined ? [] : [profile]));
|
|
40
40
|
}
|
|
41
|
-
supportsConfiguration(configuration, workspace) {
|
|
42
|
-
return (this.product(configuration.runner)?.supportsConfiguration(configuration, workspace) === true);
|
|
41
|
+
supportsConfiguration(configuration, workspace, environment) {
|
|
42
|
+
return (this.product(configuration.runner)?.supportsConfiguration(configuration, workspace, environment) === true);
|
|
43
43
|
}
|
|
44
44
|
startManagedRun(envelope) {
|
|
45
45
|
const runnerName = envelope.payload.runner;
|
|
@@ -8,7 +8,7 @@ const CLAUDE_PLAN_TAG_CLOSE = '</plan_mode>';
|
|
|
8
8
|
*/
|
|
9
9
|
export function withClaudePlanTag(input) {
|
|
10
10
|
return `${CLAUDE_PLAN_TAG_OPEN}
|
|
11
|
-
|
|
11
|
+
You are in Plan Mode. Perform only read-only analysis and planning. Do not create, modify, or delete files, and do not run commands with side effects. Wrap the complete final plan in <proposed_plan> and </proposed_plan> tags without repeating the plan outside those tags.
|
|
12
12
|
${CLAUDE_PLAN_TAG_CLOSE}
|
|
13
13
|
|
|
14
14
|
${input}`;
|
package/dist/runner-profiles.js
CHANGED
|
@@ -1,57 +1,41 @@
|
|
|
1
|
-
// Codex reasoning efforts follow the official Codex model surface. Keep this
|
|
2
|
-
// shared so an alias such as GPT-5.6 Luna cannot accidentally lose `high`.
|
|
3
|
-
const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
4
1
|
const CLAUDE_MODEL_ENVIRONMENT = {
|
|
5
2
|
opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
6
3
|
sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
7
4
|
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL'
|
|
8
5
|
};
|
|
9
6
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
14
|
-
* coupling the UI to a vendor version suffix.
|
|
7
|
+
* Keep product-visible permission modes and slash commands in one Node-owned
|
|
8
|
+
* definition. Codex models are supplied by App Server `model/list`; Claude
|
|
9
|
+
* aliases are accepted by the Agent SDK and avoid coupling the UI to a vendor
|
|
10
|
+
* version suffix.
|
|
15
11
|
*/
|
|
16
12
|
const definitions = {
|
|
17
13
|
codex: {
|
|
18
14
|
runner: 'codex',
|
|
19
|
-
models: [
|
|
20
|
-
|
|
21
|
-
id: 'gpt-5.6-sol',
|
|
22
|
-
label: 'GPT-5.6 Sol',
|
|
23
|
-
supportedEfforts: [...CODEX_REASONING_EFFORTS]
|
|
24
|
-
},
|
|
15
|
+
models: [],
|
|
16
|
+
accessOptions: [
|
|
25
17
|
{
|
|
26
|
-
id: '
|
|
27
|
-
label: '
|
|
28
|
-
|
|
18
|
+
id: 'on-request',
|
|
19
|
+
label: 'On-request',
|
|
20
|
+
description: 'Ask when additional permission is required.'
|
|
29
21
|
},
|
|
30
|
-
{
|
|
31
|
-
id: 'gpt-5.6-luna',
|
|
32
|
-
label: 'GPT-5.6 Luna',
|
|
33
|
-
supportedEfforts: [...CODEX_REASONING_EFFORTS]
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
accessOptions: [
|
|
37
|
-
{ id: 'on-request', label: 'On-request', description: '由 Codex 在需要额外权限时请求确认。' },
|
|
38
22
|
{
|
|
39
23
|
id: 'workspace-write',
|
|
40
24
|
label: 'Workspace write',
|
|
41
|
-
description: '
|
|
25
|
+
description: 'Allow writes in the current Workspace.'
|
|
42
26
|
},
|
|
43
27
|
{
|
|
44
28
|
id: 'full-access',
|
|
45
29
|
label: 'Full access',
|
|
46
|
-
description: '
|
|
30
|
+
description: 'Allow full access without individual confirmations.'
|
|
47
31
|
},
|
|
48
|
-
{ id: 'read-only', label: 'Read-only', description: '
|
|
32
|
+
{ id: 'read-only', label: 'Read-only', description: 'Restrict Codex to a read-only sandbox.' }
|
|
49
33
|
],
|
|
50
34
|
commands: [
|
|
51
35
|
{
|
|
52
36
|
id: 'compact',
|
|
53
37
|
label: '/compact',
|
|
54
|
-
description: '
|
|
38
|
+
description: 'Compact the current session context.',
|
|
55
39
|
inputHint: '/compact',
|
|
56
40
|
interaction: 'IMMEDIATE_ACTION'
|
|
57
41
|
},
|
|
@@ -59,14 +43,14 @@ const definitions = {
|
|
|
59
43
|
id: 'plan',
|
|
60
44
|
label: '/plan',
|
|
61
45
|
stateLabel: 'Plan Mode',
|
|
62
|
-
description: '
|
|
46
|
+
description: 'Toggle collaborative Plan Mode.',
|
|
63
47
|
inputHint: '/plan',
|
|
64
48
|
interaction: 'TOGGLE'
|
|
65
49
|
},
|
|
66
50
|
{
|
|
67
51
|
id: 'fast',
|
|
68
52
|
label: '/fast',
|
|
69
|
-
description: '
|
|
53
|
+
description: 'Toggle the fast service tier when supported.',
|
|
70
54
|
inputHint: '/fast',
|
|
71
55
|
interaction: 'TOGGLE'
|
|
72
56
|
}
|
|
@@ -94,21 +78,25 @@ const definitions = {
|
|
|
94
78
|
{ id: 'haiku', label: 'Claude Haiku', supportedEfforts: ['low', 'medium'] }
|
|
95
79
|
],
|
|
96
80
|
accessOptions: [
|
|
97
|
-
{ id: 'default', label: 'Default', description: '
|
|
81
|
+
{ id: 'default', label: 'Default', description: 'Use Claude Code default confirmations.' },
|
|
98
82
|
{
|
|
99
83
|
id: 'accept-edits',
|
|
100
84
|
label: 'Accept edits',
|
|
101
|
-
description: '
|
|
85
|
+
description: 'Automatically accept edits in the current Workspace.'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'bypass',
|
|
89
|
+
label: 'Bypass permissions',
|
|
90
|
+
description: 'Bypass permission confirmations.'
|
|
102
91
|
},
|
|
103
|
-
{ id: '
|
|
104
|
-
{ id: 'plan', label: 'Plan Mode', description: '以 Claude Code 的计划模式运行,不执行工具。' }
|
|
92
|
+
{ id: 'plan', label: 'Plan Mode', description: 'Run in Plan Mode without executing tools.' }
|
|
105
93
|
],
|
|
106
94
|
commands: [
|
|
107
95
|
{
|
|
108
96
|
id: 'compact',
|
|
109
97
|
runner: 'claude-code',
|
|
110
98
|
label: '/compact',
|
|
111
|
-
description: '
|
|
99
|
+
description: 'Compact context through the Claude Agent SDK.',
|
|
112
100
|
inputHint: '/compact',
|
|
113
101
|
source: 'CLAUDE_AGENT_SDK',
|
|
114
102
|
interaction: 'IMMEDIATE_ACTION',
|
|
@@ -120,7 +108,7 @@ const definitions = {
|
|
|
120
108
|
runner: 'claude-code',
|
|
121
109
|
label: '/plan',
|
|
122
110
|
stateLabel: 'Plan Mode',
|
|
123
|
-
description: '
|
|
111
|
+
description: 'Toggle read-only tagged Plan Mode.',
|
|
124
112
|
inputHint: '/plan',
|
|
125
113
|
source: 'CLAUDE_AGENT_SDK',
|
|
126
114
|
interaction: 'TOGGLE',
|
|
@@ -136,12 +124,12 @@ const definitions = {
|
|
|
136
124
|
{
|
|
137
125
|
id: 'default',
|
|
138
126
|
label: 'Default',
|
|
139
|
-
description: '
|
|
127
|
+
description: 'Use OpenCode and Workspace permission configuration.'
|
|
140
128
|
},
|
|
141
129
|
{
|
|
142
130
|
id: 'auto',
|
|
143
131
|
label: 'Auto approve',
|
|
144
|
-
description: '
|
|
132
|
+
description: 'Automatically allow requested tool permissions.'
|
|
145
133
|
}
|
|
146
134
|
],
|
|
147
135
|
commands: [
|
|
@@ -149,7 +137,7 @@ const definitions = {
|
|
|
149
137
|
id: 'compact',
|
|
150
138
|
runner: 'opencode',
|
|
151
139
|
label: '/compact',
|
|
152
|
-
description: '
|
|
140
|
+
description: 'Compact context through OpenCode.',
|
|
153
141
|
inputHint: '/compact',
|
|
154
142
|
source: 'OPENCODE_SERVER',
|
|
155
143
|
interaction: 'IMMEDIATE_ACTION',
|
|
@@ -161,7 +149,7 @@ const definitions = {
|
|
|
161
149
|
runner: 'opencode',
|
|
162
150
|
label: '/plan',
|
|
163
151
|
stateLabel: 'Plan Mode',
|
|
164
|
-
description: '
|
|
152
|
+
description: 'Toggle the native OpenCode Plan Agent.',
|
|
165
153
|
inputHint: '/plan',
|
|
166
154
|
source: 'OPENCODE_SERVER',
|
|
167
155
|
interaction: 'TOGGLE',
|
package/dist/runner-usage.js
CHANGED
|
@@ -70,9 +70,9 @@ export class RunnerUsageReader {
|
|
|
70
70
|
'Content-Type': 'application/json'
|
|
71
71
|
});
|
|
72
72
|
const windows = [
|
|
73
|
-
usageWindow(value, 'five_hour', '5
|
|
74
|
-
usageWindow(value, 'seven_day', '7
|
|
75
|
-
usageWindow(value, 'seven_day_sonnet', '7
|
|
73
|
+
usageWindow(value, 'five_hour', '5-hour limit'),
|
|
74
|
+
usageWindow(value, 'seven_day', '7-day limit'),
|
|
75
|
+
usageWindow(value, 'seven_day_sonnet', '7-day Sonnet limit')
|
|
76
76
|
].filter((window) => window !== undefined);
|
|
77
77
|
if (windows.length === 0)
|
|
78
78
|
throw new Error('USAGE_RESPONSE_INVALID');
|
|
@@ -158,9 +158,9 @@ function codexUsageWindow(value, key) {
|
|
|
158
158
|
const window = record(record(value)?.rate_limit)?.[key];
|
|
159
159
|
const duration = number(record(window)?.limit_window_seconds);
|
|
160
160
|
const label = duration !== undefined && approximately(duration, 5 * 60 * 60)
|
|
161
|
-
? '5
|
|
161
|
+
? '5-hour limit'
|
|
162
162
|
: duration !== undefined && approximately(duration, 7 * 24 * 60 * 60)
|
|
163
|
-
? '7
|
|
163
|
+
? '7-day limit'
|
|
164
164
|
: undefined;
|
|
165
165
|
return label === undefined ? undefined : usageWindow(value, key, label);
|
|
166
166
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
{ id: 'npx', candidates: ['npx'], versionArguments: ['--version'] },
|
|
6
|
+
{ id: 'git', candidates: ['git'], versionArguments: ['--version'] },
|
|
7
|
+
{ id: 'python', candidates: ['python3', 'python'], versionArguments: ['--version'] },
|
|
8
|
+
{ id: 'pip', candidates: ['pip3', 'pip'], versionArguments: ['--version'] }
|
|
9
|
+
];
|
|
10
|
+
export async function detectRuntimeCommands() {
|
|
11
|
+
return Promise.all(COMMANDS.map(detectCommand));
|
|
12
|
+
}
|
|
13
|
+
async function detectCommand(descriptor) {
|
|
14
|
+
for (const command of descriptor.candidates) {
|
|
15
|
+
try {
|
|
16
|
+
const [{ stdout, stderr }, path] = await Promise.all([
|
|
17
|
+
executeVersion(command, descriptor.versionArguments),
|
|
18
|
+
resolveCommandPath(command)
|
|
19
|
+
]);
|
|
20
|
+
const version = normalizeVersion(descriptor.id, `${stdout}${stderr}`.trim().split(/\r?\n/u)[0]?.trim() ?? '');
|
|
21
|
+
return {
|
|
22
|
+
id: descriptor.id,
|
|
23
|
+
command,
|
|
24
|
+
available: true,
|
|
25
|
+
...(version === undefined || version === '' ? {} : { version }),
|
|
26
|
+
...(path === undefined ? {} : { path })
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Try the platform-compatible fallback command name.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
id: descriptor.id,
|
|
35
|
+
command: descriptor.candidates[0],
|
|
36
|
+
available: false
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function normalizeVersion(id, value) {
|
|
40
|
+
const normalized = value.trim();
|
|
41
|
+
if (normalized === '')
|
|
42
|
+
return undefined;
|
|
43
|
+
const patterns = {
|
|
44
|
+
git: /^git version\s+(\S+)/iu,
|
|
45
|
+
python: /^python\s+(\S+)/iu,
|
|
46
|
+
pip: /^pip\s+(\S+)/iu
|
|
47
|
+
};
|
|
48
|
+
const match = patterns[id]?.exec(normalized);
|
|
49
|
+
return (match?.[1] ?? normalized).slice(0, 512);
|
|
50
|
+
}
|
|
51
|
+
async function executeVersion(command, args) {
|
|
52
|
+
const executable = process.platform === 'win32' ? process.env['ComSpec'] || 'cmd.exe' : command;
|
|
53
|
+
const commandArgs = process.platform === 'win32'
|
|
54
|
+
? ['/d', '/s', '/c', [command, ...args].map(quoteWindowsArgument).join(' ')]
|
|
55
|
+
: [...args];
|
|
56
|
+
return execFileAsync(executable, commandArgs, {
|
|
57
|
+
timeout: 5_000,
|
|
58
|
+
maxBuffer: 64 * 1024,
|
|
59
|
+
windowsHide: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function quoteWindowsArgument(value) {
|
|
63
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
64
|
+
}
|
|
65
|
+
async function resolveCommandPath(command) {
|
|
66
|
+
try {
|
|
67
|
+
const resolver = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
68
|
+
const { stdout } = await execFileAsync(resolver, [command], {
|
|
69
|
+
timeout: 2_000,
|
|
70
|
+
maxBuffer: 64 * 1024,
|
|
71
|
+
windowsHide: true
|
|
72
|
+
});
|
|
73
|
+
return stdout.trim().split(/\r?\n/u)[0]?.trim().slice(0, 2048) || undefined;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -16,35 +16,66 @@ export class ConversationHistoryService {
|
|
|
16
16
|
async read(session, input) {
|
|
17
17
|
const snapshotSequence = this.options.snapshotSequence();
|
|
18
18
|
const readAt = Date.now();
|
|
19
|
-
|
|
19
|
+
let official = await this.readOfficial(session, input);
|
|
20
20
|
if (official !== undefined) {
|
|
21
|
+
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
22
|
+
const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
|
|
23
|
+
? []
|
|
24
|
+
: this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
|
|
25
|
+
const initialOfficialRunIds = new Set(official.turns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
|
|
26
|
+
const absentLocalTurns = runtimeTurns.filter((turn) => turn.runId === null || !initialOfficialRunIds.has(turn.runId));
|
|
27
|
+
const officialFloor = Math.min(...official.turns.map((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0)));
|
|
28
|
+
const missingLocalTurns = official.turns.length === 0
|
|
29
|
+
? absentLocalTurns.slice(-limit)
|
|
30
|
+
: officialFloor > 0
|
|
31
|
+
? absentLocalTurns.filter((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0) >= officialFloor)
|
|
32
|
+
: absentLocalTurns.slice(-1);
|
|
33
|
+
if (input.cursor === undefined &&
|
|
34
|
+
missingLocalTurns.length > 0 &&
|
|
35
|
+
official.turns.length + missingLocalTurns.length > limit) {
|
|
36
|
+
const reserved = Math.min(missingLocalTurns.length, Math.max(0, limit - 1));
|
|
37
|
+
const narrowed = await this.readOfficial(session, { ...input, limit: limit - reserved });
|
|
38
|
+
if (narrowed !== undefined)
|
|
39
|
+
official = narrowed;
|
|
40
|
+
}
|
|
21
41
|
const officialTurns = await this.attachNativeImages(session, official.turns);
|
|
22
42
|
const officialRunIds = new Set(officialTurns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
const pendingItemsByRunId = new Map();
|
|
44
|
+
for (const turn of runtimeTurns) {
|
|
45
|
+
if (turn.runId === null || !officialRunIds.has(turn.runId))
|
|
46
|
+
continue;
|
|
47
|
+
const pendingItems = turn.items.filter((item) => item.kind === 'user_input_request' && item.status === 'PENDING');
|
|
48
|
+
if (pendingItems.length > 0)
|
|
49
|
+
pendingItemsByRunId.set(turn.runId, pendingItems);
|
|
50
|
+
}
|
|
51
|
+
const availableLocalSlots = Math.max(0, limit - officialTurns.length);
|
|
52
|
+
const localCandidates = (input.cursor === undefined ? missingLocalTurns : []).sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0));
|
|
53
|
+
const localTurns = availableLocalSlots === 0 ? [] : localCandidates.slice(-availableLocalSlots);
|
|
54
|
+
const localRequestIds = new Set([
|
|
55
|
+
...localTurns.flatMap((turn) => turn.items.flatMap((item) => {
|
|
56
|
+
const requestId = item.kind === 'user_input_request' && isPlainRecord(item.payload)
|
|
57
|
+
? item.payload.requestId
|
|
58
|
+
: undefined;
|
|
59
|
+
return typeof requestId === 'string' ? [requestId] : [];
|
|
60
|
+
})),
|
|
61
|
+
...[...pendingItemsByRunId.values()].flatMap((items) => items.flatMap((item) => {
|
|
62
|
+
const requestId = isPlainRecord(item.payload) ? item.payload.requestId : undefined;
|
|
63
|
+
return typeof requestId === 'string' ? [requestId] : [];
|
|
64
|
+
}))
|
|
65
|
+
]);
|
|
39
66
|
const turns = [
|
|
40
67
|
...officialTurns
|
|
41
|
-
.map((turn) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
.map((turn) => {
|
|
69
|
+
const pendingItems = turn.runId === null ? [] : (pendingItemsByRunId.get(turn.runId) ?? []);
|
|
70
|
+
const items = [
|
|
71
|
+
...turn.items.filter((item) => item.kind !== 'user_input_request' ||
|
|
72
|
+
!isPlainRecord(item.payload) ||
|
|
73
|
+
typeof item.payload.requestId !== 'string' ||
|
|
74
|
+
!localRequestIds.has(item.payload.requestId)),
|
|
75
|
+
...pendingItems
|
|
76
|
+
];
|
|
77
|
+
return { ...turn, items };
|
|
78
|
+
})
|
|
48
79
|
.filter((turn) => turn.items.length > 0),
|
|
49
80
|
...localTurns
|
|
50
81
|
].sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ConversationSegment, ConversationSegmentPage } from '@myagentroam/protocol';
|
|
2
|
+
import type { NodeAgentSession, NodeConversationTurn } from '../database.js';
|
|
3
|
+
import type { ConversationHistoryPage } from '../util/runner-native-session-parsers.js';
|
|
4
|
+
export declare function conversationSegments(turn: NodeConversationTurn): ConversationSegment[];
|
|
5
|
+
export declare function conversationSegmentIdentityForItem(turn: NodeConversationTurn, itemId: string, itemKind?: string): {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly index: number;
|
|
8
|
+
};
|
|
9
|
+
export declare class ConversationSegmentService {
|
|
10
|
+
private readonly readTurns;
|
|
11
|
+
constructor(readTurns: (session: NodeAgentSession, input: {
|
|
12
|
+
readonly cursor?: string;
|
|
13
|
+
readonly limit?: number;
|
|
14
|
+
}) => Promise<ConversationHistoryPage>);
|
|
15
|
+
projectInitial(session: NodeAgentSession, page: ConversationHistoryPage, limit: number): ConversationSegmentPage;
|
|
16
|
+
read(session: NodeAgentSession, input: {
|
|
17
|
+
readonly cursor?: string;
|
|
18
|
+
readonly limit?: number;
|
|
19
|
+
}): Promise<ConversationSegmentPage>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const TURN_PAGE_SIZE = 30;
|
|
2
|
+
export function conversationSegments(turn) {
|
|
3
|
+
const groups = [];
|
|
4
|
+
let current = [];
|
|
5
|
+
for (const item of turn.items) {
|
|
6
|
+
current.push(item);
|
|
7
|
+
if (item.kind !== 'assistant_message')
|
|
8
|
+
continue;
|
|
9
|
+
groups.push(current);
|
|
10
|
+
current = [];
|
|
11
|
+
}
|
|
12
|
+
if (current.length > 0 || groups.length === 0)
|
|
13
|
+
groups.push(current);
|
|
14
|
+
return groups.map((items, index) => {
|
|
15
|
+
const id = `${turn.id}:segment:${index}`;
|
|
16
|
+
const latest = index === groups.length - 1;
|
|
17
|
+
const projectedItems = items.map((item) => ({
|
|
18
|
+
...item,
|
|
19
|
+
segmentId: id,
|
|
20
|
+
segmentIndex: index
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
sessionId: turn.sessionId,
|
|
25
|
+
turnId: turn.id,
|
|
26
|
+
runId: turn.runId,
|
|
27
|
+
index,
|
|
28
|
+
status: (latest ? turn.status : 'SUCCEEDED'),
|
|
29
|
+
...(latest && turn.rewind !== undefined ? { rewind: turn.rewind } : {}),
|
|
30
|
+
items: projectedItems,
|
|
31
|
+
startedAt: projectedItems[0]?.startedAt ?? turn.startedAt,
|
|
32
|
+
completedAt: projectedItems.at(-1)?.completedAt ?? null
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function conversationSegmentIdentityForItem(turn, itemId, itemKind) {
|
|
37
|
+
let index = 0;
|
|
38
|
+
const runtimeItemId = turn.runId === null ? itemId : `${turn.runId}:${itemId}`;
|
|
39
|
+
for (const item of turn.items) {
|
|
40
|
+
if (item.id === itemId || item.id === runtimeItemId)
|
|
41
|
+
return { id: `${turn.id}:segment:${index}`, index };
|
|
42
|
+
if (item.kind === 'assistant_message')
|
|
43
|
+
index += 1;
|
|
44
|
+
}
|
|
45
|
+
if (itemKind === 'assistant_message' && turn.items.at(-1)?.kind === 'assistant_message')
|
|
46
|
+
index -= 1;
|
|
47
|
+
return { id: `${turn.id}:segment:${index}`, index };
|
|
48
|
+
}
|
|
49
|
+
export class ConversationSegmentService {
|
|
50
|
+
readTurns;
|
|
51
|
+
constructor(readTurns) {
|
|
52
|
+
this.readTurns = readTurns;
|
|
53
|
+
}
|
|
54
|
+
projectInitial(session, page, limit) {
|
|
55
|
+
const all = page.turns.flatMap(conversationSegments);
|
|
56
|
+
const segments = all.slice(-limit);
|
|
57
|
+
return {
|
|
58
|
+
snapshotSequence: page.snapshotSequence,
|
|
59
|
+
source: page.source,
|
|
60
|
+
readAt: page.readAt,
|
|
61
|
+
latestVisibleAt: page.latestVisibleAt,
|
|
62
|
+
unit: 'SEGMENT',
|
|
63
|
+
segments,
|
|
64
|
+
nextCursor: segments.length === 0 || (segments.length === all.length && page.nextCursor === null)
|
|
65
|
+
? null
|
|
66
|
+
: encodeCursor({
|
|
67
|
+
version: 1,
|
|
68
|
+
sessionId: session.id,
|
|
69
|
+
beforeSegmentId: segments[0].id
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async read(session, input) {
|
|
74
|
+
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
75
|
+
const boundary = input.cursor === undefined ? undefined : decodeCursor(input.cursor, session.id);
|
|
76
|
+
const collected = [];
|
|
77
|
+
const visited = new Set();
|
|
78
|
+
let turnCursor;
|
|
79
|
+
let locatingBoundary = boundary !== undefined;
|
|
80
|
+
let hasOlder = false;
|
|
81
|
+
let metadata;
|
|
82
|
+
while (collected.length < limit) {
|
|
83
|
+
const page = await this.readTurns(session, {
|
|
84
|
+
...(turnCursor === undefined ? {} : { cursor: turnCursor }),
|
|
85
|
+
limit: TURN_PAGE_SIZE
|
|
86
|
+
});
|
|
87
|
+
metadata ??= {
|
|
88
|
+
snapshotSequence: page.snapshotSequence,
|
|
89
|
+
source: page.source,
|
|
90
|
+
readAt: page.readAt,
|
|
91
|
+
latestVisibleAt: page.latestVisibleAt
|
|
92
|
+
};
|
|
93
|
+
const pageSegments = page.turns.flatMap(conversationSegments);
|
|
94
|
+
let eligible = pageSegments;
|
|
95
|
+
if (locatingBoundary) {
|
|
96
|
+
const boundaryIndex = pageSegments.findIndex((segment) => segment.id === boundary?.beforeSegmentId);
|
|
97
|
+
if (boundaryIndex >= 0) {
|
|
98
|
+
eligible = pageSegments.slice(0, boundaryIndex);
|
|
99
|
+
locatingBoundary = false;
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
eligible = [];
|
|
103
|
+
}
|
|
104
|
+
if (!locatingBoundary && eligible.length > 0) {
|
|
105
|
+
const remaining = Math.max(0, limit - collected.length);
|
|
106
|
+
const take = eligible.slice(-remaining);
|
|
107
|
+
hasOlder = eligible.length > take.length || page.nextCursor !== null;
|
|
108
|
+
collected.unshift(...take);
|
|
109
|
+
}
|
|
110
|
+
if (collected.length >= limit || page.nextCursor === null)
|
|
111
|
+
break;
|
|
112
|
+
if (visited.has(page.nextCursor))
|
|
113
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
114
|
+
visited.add(page.nextCursor);
|
|
115
|
+
turnCursor = page.nextCursor;
|
|
116
|
+
}
|
|
117
|
+
if (locatingBoundary)
|
|
118
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
119
|
+
const nextCursor = collected.length === 0 || !hasOlder
|
|
120
|
+
? null
|
|
121
|
+
: encodeCursor({
|
|
122
|
+
version: 1,
|
|
123
|
+
sessionId: session.id,
|
|
124
|
+
beforeSegmentId: collected[0].id
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
...(metadata ?? {
|
|
128
|
+
snapshotSequence: 0,
|
|
129
|
+
source: 'runtime',
|
|
130
|
+
readAt: Date.now(),
|
|
131
|
+
latestVisibleAt: 0
|
|
132
|
+
}),
|
|
133
|
+
unit: 'SEGMENT',
|
|
134
|
+
segments: collected,
|
|
135
|
+
nextCursor
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function encodeCursor(cursor) {
|
|
140
|
+
return Buffer.from(JSON.stringify(cursor)).toString('base64url');
|
|
141
|
+
}
|
|
142
|
+
function decodeCursor(value, sessionId) {
|
|
143
|
+
try {
|
|
144
|
+
const decoded = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
145
|
+
if (decoded.version !== 1 ||
|
|
146
|
+
decoded.sessionId !== sessionId ||
|
|
147
|
+
typeof decoded.beforeSegmentId !== 'string' ||
|
|
148
|
+
decoded.beforeSegmentId.length === 0)
|
|
149
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
150
|
+
return decoded;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpCatalogEntry, McpInstallationConfiguration } from '@myagentroam/protocol';
|
|
2
|
+
export declare class McpInstallationVerifier {
|
|
3
|
+
verify(input: {
|
|
4
|
+
readonly entry: McpCatalogEntry;
|
|
5
|
+
readonly configuration: McpInstallationConfiguration;
|
|
6
|
+
readonly secrets: Readonly<Record<string, string>>;
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
}
|