salmon-loop 0.3.0 → 0.3.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/cli/authorization/non-interactive.js +7 -21
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/parallel.js +46 -41
- package/dist/cli/commands/run/assistant-message.js +3 -0
- package/dist/cli/commands/run/handler.js +2 -1
- package/dist/cli/commands/serve.js +123 -154
- package/dist/cli/headless/json-protocol.js +1 -1
- package/dist/cli/headless/stream-json-protocol.js +3 -2
- package/dist/cli/slash/runtime.js +5 -1
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/core/adapters/fs/node-fs.js +1 -0
- package/dist/core/benchmark/patch-artifact.js +1 -1
- package/dist/core/context/service.js +36 -10
- package/dist/core/extensions/index.js +2 -35
- package/dist/core/extensions/redact.js +9 -3
- package/dist/core/extensions/schemas.js +2 -51
- package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
- package/dist/core/facades/cli-serve.js +0 -1
- package/dist/core/grizzco/dsl/strategies.js +1 -3
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
- package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
- package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
- package/dist/core/grizzco/steps/apply.js +0 -7
- package/dist/core/grizzco/steps/autopilot.js +108 -6
- package/dist/core/grizzco/steps/preflight.js +10 -0
- package/dist/core/grizzco/steps/tool-runtime.js +1 -0
- package/dist/core/interaction/events/bus.js +14 -0
- package/dist/core/interaction/orchestration/facade.js +10 -0
- package/dist/core/mcp/bridge/index.js +4 -0
- package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
- package/dist/core/mcp/bridge/tool-bridge.js +303 -0
- package/dist/core/mcp/cache/resource-cache.js +41 -0
- package/dist/core/mcp/catalog/discovery.js +51 -0
- package/dist/core/mcp/catalog/notification-router.js +28 -0
- package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
- package/dist/core/mcp/catalog/resource-catalog.js +7 -0
- package/dist/core/mcp/catalog/tool-catalog.js +4 -0
- package/dist/core/mcp/client/connection-manager.js +239 -0
- package/dist/core/mcp/client/lifecycle.js +13 -0
- package/dist/core/mcp/client/transport-factory.js +168 -0
- package/dist/core/mcp/config/index.js +32 -0
- package/dist/core/mcp/config/schema-v2.js +129 -0
- package/dist/core/mcp/host/elicitation-provider.js +209 -0
- package/dist/core/mcp/host/roots-provider.js +70 -0
- package/dist/core/mcp/host/sampling-provider.js +170 -0
- package/dist/core/mcp/index.js +4 -0
- package/dist/core/mcp/observability/events.js +19 -0
- package/dist/core/mcp/policy/approval-policy.js +2 -0
- package/dist/core/mcp/policy/classifier.js +172 -0
- package/dist/core/mcp/policy/grants.js +356 -0
- package/dist/core/mcp/policy/uri-policy.js +60 -0
- package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
- package/dist/core/mcp/types.js +2 -0
- package/dist/core/protocols/a2a/agent-card.js +36 -11
- package/dist/core/protocols/a2a/sdk/executor.js +105 -36
- package/dist/core/protocols/a2a/sdk/server.js +1311 -3
- package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
- package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
- package/dist/core/protocols/acp/acp-types.js +17 -0
- package/dist/core/protocols/acp/formal-agent.js +271 -603
- package/dist/core/protocols/acp/handlers.js +3 -0
- package/dist/core/protocols/acp/permission-provider.js +11 -39
- package/dist/core/protocols/acp/stdio-server.js +20 -1
- package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
- package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
- package/dist/core/public-capabilities/projections.js +1 -0
- package/dist/core/runtime/agent-server-runtime.js +2 -3
- package/dist/core/runtime/spawn-command.js +8 -2
- package/dist/core/runtime/spawn-interactive.js +26 -0
- package/dist/core/session/manager.js +65 -35
- package/dist/core/tools/builtin/index.js +6 -1
- package/dist/core/tools/builtin/proposal.js +0 -7
- package/dist/core/tools/builtin/workspace.js +76 -0
- package/dist/core/tools/dispatcher.js +1 -0
- package/dist/core/tools/loader.js +92 -46
- package/dist/core/verification/runner.js +60 -31
- package/dist/core/workspace/capabilities.js +80 -0
- package/dist/locales/en.js +17 -3
- package/package.json +4 -2
- package/dist/core/protocols/a2a/mapper.js +0 -14
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
- package/dist/core/protocols/a2a/task-projection.js +0 -45
- package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
- package/dist/core/tools/mcp/client.js +0 -309
- package/dist/core/tools/mcp/loader.js +0 -110
- package/dist/core/tools/mcp/schema.js +0 -54
- package/dist/core/tools/mcp/streamable-http.js +0 -101
- package/dist/core/tools/mcp/types.js +0 -26
|
@@ -12,7 +12,10 @@ export function createAcpSessionStore() {
|
|
|
12
12
|
createdAt: now,
|
|
13
13
|
updatedAt: now,
|
|
14
14
|
title: input.title,
|
|
15
|
+
permissionPolicy: input.permissionPolicy,
|
|
16
|
+
modeId: input.modeId,
|
|
15
17
|
history: [],
|
|
18
|
+
materialized: false,
|
|
16
19
|
cancelRequested: false,
|
|
17
20
|
};
|
|
18
21
|
sessions.set(id, session);
|
|
@@ -1,43 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (name.includes('read') ||
|
|
4
|
-
name.includes('get') ||
|
|
5
|
-
name.includes('view') ||
|
|
6
|
-
name.includes('ls') ||
|
|
7
|
-
name.includes('list') ||
|
|
8
|
-
(request.sideEffects.length > 0 && request.sideEffects.every((e) => e === 'fs_read')))
|
|
9
|
-
return 'read';
|
|
10
|
-
if (name.includes('write') ||
|
|
11
|
-
name.includes('edit') ||
|
|
12
|
-
name.includes('patch') ||
|
|
13
|
-
request.sideEffects.includes('fs_write'))
|
|
14
|
-
return 'edit';
|
|
15
|
-
if (name.includes('delete') ||
|
|
16
|
-
name.includes('remove') ||
|
|
17
|
-
name.includes('rm') ||
|
|
18
|
-
request.sideEffects.includes('fs_delete'))
|
|
19
|
-
return 'delete';
|
|
20
|
-
if (name.includes('move') || name.includes('rename') || name.includes('mv'))
|
|
21
|
-
return 'move';
|
|
22
|
-
if (name.includes('grep') || name.includes('search') || name.includes('find'))
|
|
23
|
-
return 'search';
|
|
24
|
-
if (name.includes('run') ||
|
|
25
|
-
name.includes('exec') ||
|
|
26
|
-
name.includes('spawn') ||
|
|
27
|
-
request.sideEffects.includes('process'))
|
|
28
|
-
return 'execute';
|
|
29
|
-
if (name.includes('plan') || name.includes('think') || name.includes('reason'))
|
|
30
|
-
return 'think';
|
|
31
|
-
if (name.includes('fetch') || name.includes('curl') || name.includes('http'))
|
|
32
|
-
return 'fetch';
|
|
33
|
-
return 'other';
|
|
34
|
-
}
|
|
1
|
+
import { text } from '../../../locales/index.js';
|
|
2
|
+
import { mapToolKind } from './tool-kind-mapping.js';
|
|
35
3
|
function buildPermissionOptions() {
|
|
36
4
|
return [
|
|
37
|
-
{ kind: 'allow_once', name:
|
|
38
|
-
{ kind: 'allow_always', name:
|
|
39
|
-
{ kind: 'reject_once', name:
|
|
40
|
-
{
|
|
5
|
+
{ kind: 'allow_once', name: text.acp.permissionOptionAllowOnce, optionId: 'allow_once' },
|
|
6
|
+
{ kind: 'allow_always', name: text.acp.permissionOptionAllowSession, optionId: 'allow_always' },
|
|
7
|
+
{ kind: 'reject_once', name: text.acp.permissionOptionRejectOnce, optionId: 'reject_once' },
|
|
8
|
+
{
|
|
9
|
+
kind: 'reject_always',
|
|
10
|
+
name: text.acp.permissionOptionRejectSession,
|
|
11
|
+
optionId: 'reject_always',
|
|
12
|
+
},
|
|
41
13
|
];
|
|
42
14
|
}
|
|
43
15
|
function toToolCallUpdate(request) {
|
|
@@ -48,7 +20,7 @@ function toToolCallUpdate(request) {
|
|
|
48
20
|
return {
|
|
49
21
|
toolCallId: request.id,
|
|
50
22
|
title: request.toolName,
|
|
51
|
-
kind:
|
|
23
|
+
kind: mapToolKind(request.toolName, { sideEffects: request.sideEffects }),
|
|
52
24
|
status: 'pending',
|
|
53
25
|
rawInput: {
|
|
54
26
|
toolName: request.toolName,
|
|
@@ -14,11 +14,21 @@ const PARSE_ERROR = {
|
|
|
14
14
|
function isJsonObject(value) {
|
|
15
15
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
16
|
}
|
|
17
|
+
function hasOwn(value, key) {
|
|
18
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
19
|
+
}
|
|
17
20
|
function formatLineSnippet(line, maxLength = 160) {
|
|
18
21
|
if (line.length <= maxLength)
|
|
19
22
|
return line;
|
|
20
23
|
return `${line.slice(0, maxLength - 3)}...`;
|
|
21
24
|
}
|
|
25
|
+
function normalizeJsonRpcParams(message) {
|
|
26
|
+
if (typeof message.method !== 'string')
|
|
27
|
+
return;
|
|
28
|
+
if (!hasOwn(message, 'params') || message.params === null) {
|
|
29
|
+
message.params = {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
function safeWarn(message) {
|
|
23
33
|
const logger = tryGetLogger();
|
|
24
34
|
if (logger)
|
|
@@ -28,8 +38,12 @@ function createNdjsonWriter(output) {
|
|
|
28
38
|
const writer = output.getWriter();
|
|
29
39
|
const encoder = new TextEncoder();
|
|
30
40
|
let tail = Promise.resolve();
|
|
41
|
+
let lastError;
|
|
31
42
|
return {
|
|
32
43
|
async write(message) {
|
|
44
|
+
if (lastError) {
|
|
45
|
+
throw lastError;
|
|
46
|
+
}
|
|
33
47
|
const content = JSON.stringify(message) + '\n';
|
|
34
48
|
const data = encoder.encode(content);
|
|
35
49
|
tail = tail
|
|
@@ -38,6 +52,7 @@ function createNdjsonWriter(output) {
|
|
|
38
52
|
.catch((error) => {
|
|
39
53
|
const detail = error instanceof Error ? error.message : String(error);
|
|
40
54
|
safeWarn(`ACP stdio failed to write NDJSON line. reason="${detail}"`);
|
|
55
|
+
lastError = error instanceof Error ? new Error(detail) : new Error(detail);
|
|
41
56
|
});
|
|
42
57
|
await tail;
|
|
43
58
|
},
|
|
@@ -58,6 +73,7 @@ async function processStdioLine(line, ndjson, controller) {
|
|
|
58
73
|
await writeInvalidRequest(ndjson, trimmed);
|
|
59
74
|
return;
|
|
60
75
|
}
|
|
76
|
+
normalizeJsonRpcParams(parsed);
|
|
61
77
|
if (parsed.params && typeof parsed.params === 'object' && !Array.isArray(parsed.params)) {
|
|
62
78
|
const params = parsed.params;
|
|
63
79
|
if (params.mcpServers === null) {
|
|
@@ -82,8 +98,10 @@ export function createAcpStdioStream(output, input) {
|
|
|
82
98
|
try {
|
|
83
99
|
while (true) {
|
|
84
100
|
const { value, done } = await reader.read();
|
|
85
|
-
if (done)
|
|
101
|
+
if (done) {
|
|
102
|
+
buffer += textDecoder.decode();
|
|
86
103
|
break;
|
|
104
|
+
}
|
|
87
105
|
if (!value)
|
|
88
106
|
continue;
|
|
89
107
|
buffer += textDecoder.decode(value, { stream: true });
|
|
@@ -93,6 +111,7 @@ export function createAcpStdioStream(output, input) {
|
|
|
93
111
|
await processStdioLine(line, ndjson, controller);
|
|
94
112
|
}
|
|
95
113
|
}
|
|
114
|
+
await processStdioLine(buffer, ndjson, controller);
|
|
96
115
|
}
|
|
97
116
|
finally {
|
|
98
117
|
reader.releaseLock();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a tool name and optional metadata to an ACP ToolKind.
|
|
3
|
+
*
|
|
4
|
+
* Priority: explicit intent > side-effect inference > name heuristics.
|
|
5
|
+
*
|
|
6
|
+
* Covers all 10 protocol-defined kinds:
|
|
7
|
+
* read | edit | delete | move | search | execute | think | fetch | switch_mode | other
|
|
8
|
+
*/
|
|
9
|
+
export function mapToolKind(toolName, options) {
|
|
10
|
+
const intent = options?.intent;
|
|
11
|
+
if (intent) {
|
|
12
|
+
switch (intent.toUpperCase()) {
|
|
13
|
+
case 'READ':
|
|
14
|
+
case 'LIST':
|
|
15
|
+
return 'read';
|
|
16
|
+
case 'SEARCH':
|
|
17
|
+
return 'search';
|
|
18
|
+
case 'WRITE':
|
|
19
|
+
return 'edit';
|
|
20
|
+
case 'INFRA':
|
|
21
|
+
return 'execute';
|
|
22
|
+
case 'AGENT':
|
|
23
|
+
return 'think';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const sideEffects = options?.sideEffects;
|
|
27
|
+
if (sideEffects && sideEffects.length > 0) {
|
|
28
|
+
if (sideEffects.every((e) => e === 'fs_read'))
|
|
29
|
+
return 'read';
|
|
30
|
+
if (sideEffects.includes('fs_write'))
|
|
31
|
+
return 'edit';
|
|
32
|
+
if (sideEffects.includes('fs_delete'))
|
|
33
|
+
return 'delete';
|
|
34
|
+
if (sideEffects.includes('process'))
|
|
35
|
+
return 'execute';
|
|
36
|
+
}
|
|
37
|
+
const name = toolName.toLowerCase();
|
|
38
|
+
if (name.includes('read') ||
|
|
39
|
+
name.includes('get') ||
|
|
40
|
+
name.includes('view') ||
|
|
41
|
+
name.includes('ls') ||
|
|
42
|
+
name.includes('list'))
|
|
43
|
+
return 'read';
|
|
44
|
+
if (name.includes('write') || name.includes('edit') || name.includes('patch'))
|
|
45
|
+
return 'edit';
|
|
46
|
+
if (name.includes('delete') || name.includes('remove') || name.includes('rm'))
|
|
47
|
+
return 'delete';
|
|
48
|
+
if (name.includes('move') || name.includes('rename') || name.includes('mv'))
|
|
49
|
+
return 'move';
|
|
50
|
+
if (name.includes('grep') || name.includes('search') || name.includes('find'))
|
|
51
|
+
return 'search';
|
|
52
|
+
if (name.includes('run') || name.includes('exec') || name.includes('spawn'))
|
|
53
|
+
return 'execute';
|
|
54
|
+
if (name.includes('plan') || name.includes('think') || name.includes('reason'))
|
|
55
|
+
return 'think';
|
|
56
|
+
if (name.includes('fetch') || name.includes('curl') || name.includes('http'))
|
|
57
|
+
return 'fetch';
|
|
58
|
+
if (name.includes('mode') || name.includes('switch'))
|
|
59
|
+
return 'switch_mode';
|
|
60
|
+
return 'other';
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=tool-kind-mapping.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FLOW_MODE_PUBLIC_METADATA } from '../../public-capabilities/flow-mode-metadata.js';
|
|
2
1
|
import { FLOW_MODES, parseFlowMode } from '../../types/flow-mode.js';
|
|
3
2
|
export const SUPPORTED_PROTOCOL_FLOW_MODES = FLOW_MODES;
|
|
4
3
|
export function parseAcpFlowMode(value) {
|
|
@@ -13,11 +12,4 @@ export function parseAcpFlowMode(value) {
|
|
|
13
12
|
export function parseA2ASkillFlowMode(value) {
|
|
14
13
|
return parseFlowMode(value);
|
|
15
14
|
}
|
|
16
|
-
export function buildA2AFlowSkills() {
|
|
17
|
-
return SUPPORTED_PROTOCOL_FLOW_MODES.map((mode) => ({
|
|
18
|
-
id: mode,
|
|
19
|
-
title: FLOW_MODE_PUBLIC_METADATA[mode].a2aTitle,
|
|
20
|
-
description: FLOW_MODE_PUBLIC_METADATA[mode].description,
|
|
21
|
-
}));
|
|
22
|
-
}
|
|
23
15
|
//# sourceMappingURL=flow-mode-mapping.js.map
|
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
export const FLOW_MODE_PUBLIC_METADATA = {
|
|
2
2
|
autopilot: {
|
|
3
3
|
publicTitle: 'Autopilot',
|
|
4
|
-
a2aTitle: 'Autopilot',
|
|
5
4
|
acpName: 'Autopilot',
|
|
6
5
|
description: 'Let the agent decide which actions and tools to use.',
|
|
7
6
|
},
|
|
8
7
|
patch: {
|
|
9
8
|
publicTitle: 'Patch code',
|
|
10
|
-
a2aTitle: 'Patch code',
|
|
11
9
|
acpName: 'Patch',
|
|
12
10
|
description: 'Apply code changes with verification.',
|
|
13
11
|
},
|
|
14
12
|
review: {
|
|
15
13
|
publicTitle: 'Review code',
|
|
16
|
-
a2aTitle: 'Review code',
|
|
17
14
|
acpName: 'Review',
|
|
18
15
|
description: 'Inspect code and report findings without mutating files.',
|
|
19
16
|
},
|
|
20
17
|
debug: {
|
|
21
18
|
publicTitle: 'Debug issue',
|
|
22
|
-
a2aTitle: 'Debug issue',
|
|
23
19
|
acpName: 'Debug',
|
|
24
20
|
description: 'Investigate issues and make targeted fixes when needed.',
|
|
25
21
|
},
|
|
26
22
|
research: {
|
|
27
23
|
publicTitle: 'Research request',
|
|
28
|
-
a2aTitle: 'Research request',
|
|
29
24
|
acpName: 'Research',
|
|
30
25
|
description: 'Explore the codebase and summarize relevant findings.',
|
|
31
26
|
},
|
|
32
27
|
answer: {
|
|
33
28
|
publicTitle: 'Answer question',
|
|
34
|
-
a2aTitle: 'Answer question',
|
|
35
29
|
acpName: 'Answer',
|
|
36
30
|
description: 'Answer questions directly without editing files.',
|
|
37
31
|
},
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { InMemoryTaskStore } from '@a2a-js/sdk/server';
|
|
2
1
|
import { createTaskEventBus } from '../interaction/events/bus.js';
|
|
3
2
|
import { createInteractionFacade } from '../interaction/orchestration/facade.js';
|
|
4
3
|
import { createA2AInteractionExecutor } from '../protocols/a2a/sdk/executor.js';
|
|
5
|
-
import { createA2ASdkExpressApp } from '../protocols/a2a/sdk/server.js';
|
|
4
|
+
import { createA2ASdkExpressApp, ProtocolAlignedInMemoryTaskStore, } from '../protocols/a2a/sdk/server.js';
|
|
6
5
|
export function createAgentServerRuntime(deps) {
|
|
7
6
|
const eventBus = deps.a2a.eventBus ?? createTaskEventBus();
|
|
8
|
-
const taskStore = deps.a2a.taskStore ?? new
|
|
7
|
+
const taskStore = deps.a2a.taskStore ?? new ProtocolAlignedInMemoryTaskStore();
|
|
9
8
|
const facade = createInteractionFacade({
|
|
10
9
|
executeTask: deps.a2a.executeTask,
|
|
11
10
|
eventBus,
|
|
@@ -349,14 +349,20 @@ async function spawnWithNode(input) {
|
|
|
349
349
|
timeoutTimer = setTimeout(() => {
|
|
350
350
|
timedOut = true;
|
|
351
351
|
killProcess('SIGTERM');
|
|
352
|
-
killTimer = setTimeout(() =>
|
|
352
|
+
killTimer = setTimeout(() => {
|
|
353
|
+
killProcess('SIGKILL');
|
|
354
|
+
settle(null, null);
|
|
355
|
+
}, killGraceMs);
|
|
353
356
|
}, input.timeoutMs);
|
|
354
357
|
}
|
|
355
358
|
if (input.signal) {
|
|
356
359
|
onAbort = () => {
|
|
357
360
|
aborted = true;
|
|
358
361
|
killProcess('SIGTERM');
|
|
359
|
-
killTimer = setTimeout(() =>
|
|
362
|
+
killTimer = setTimeout(() => {
|
|
363
|
+
killProcess('SIGKILL');
|
|
364
|
+
settle(null, null);
|
|
365
|
+
}, input.killGraceMs ?? 2000);
|
|
360
366
|
};
|
|
361
367
|
input.signal.addEventListener('abort', onAbort, { once: true });
|
|
362
368
|
}
|
|
@@ -14,15 +14,22 @@ export function spawnInteractiveProcess(input) {
|
|
|
14
14
|
windowsHide: input.windowsHide,
|
|
15
15
|
});
|
|
16
16
|
const events = new EventEmitter();
|
|
17
|
+
let exitCode;
|
|
18
|
+
queueMicrotask(() => events.emit('spawn'));
|
|
17
19
|
void subprocess.exited
|
|
18
20
|
.then((code) => {
|
|
21
|
+
exitCode = code;
|
|
19
22
|
events.emit('exit', code, normalizeSignal(subprocess.signalCode));
|
|
23
|
+
events.emit('close', code, normalizeSignal(subprocess.signalCode));
|
|
20
24
|
})
|
|
21
25
|
.catch((error) => {
|
|
22
26
|
events.emit('error', error);
|
|
23
27
|
});
|
|
24
28
|
const processRef = {
|
|
25
29
|
pid: subprocess.pid,
|
|
30
|
+
get exitCode() {
|
|
31
|
+
return exitCode ?? null;
|
|
32
|
+
},
|
|
26
33
|
stdin: subprocess.stdin ?? undefined,
|
|
27
34
|
stdout: toNodeReadableStream(subprocess.stdout),
|
|
28
35
|
stderr: toNodeReadableStream(subprocess.stderr),
|
|
@@ -38,6 +45,14 @@ export function spawnInteractiveProcess(input) {
|
|
|
38
45
|
events.on(event, listener);
|
|
39
46
|
return processRef;
|
|
40
47
|
},
|
|
48
|
+
once: (event, listener) => {
|
|
49
|
+
events.once(event, listener);
|
|
50
|
+
return processRef;
|
|
51
|
+
},
|
|
52
|
+
off: (event, listener) => {
|
|
53
|
+
events.off(event, listener);
|
|
54
|
+
return processRef;
|
|
55
|
+
},
|
|
41
56
|
};
|
|
42
57
|
return processRef;
|
|
43
58
|
}
|
|
@@ -50,6 +65,9 @@ export function spawnInteractiveProcess(input) {
|
|
|
50
65
|
});
|
|
51
66
|
const processRef = {
|
|
52
67
|
pid: child.pid,
|
|
68
|
+
get exitCode() {
|
|
69
|
+
return child.exitCode;
|
|
70
|
+
},
|
|
53
71
|
stdin: child.stdin ?? undefined,
|
|
54
72
|
stdout: child.stdout ?? undefined,
|
|
55
73
|
stderr: child.stderr ?? undefined,
|
|
@@ -65,6 +83,14 @@ export function spawnInteractiveProcess(input) {
|
|
|
65
83
|
child.on(event, listener);
|
|
66
84
|
return processRef;
|
|
67
85
|
},
|
|
86
|
+
once: (event, listener) => {
|
|
87
|
+
child.once(event, listener);
|
|
88
|
+
return processRef;
|
|
89
|
+
},
|
|
90
|
+
off: (event, listener) => {
|
|
91
|
+
child.off(event, listener);
|
|
92
|
+
return processRef;
|
|
93
|
+
},
|
|
68
94
|
};
|
|
69
95
|
return processRef;
|
|
70
96
|
}
|
|
@@ -45,6 +45,7 @@ function normalizeChatState(chatState) {
|
|
|
45
45
|
* Features: Auto-pruning, compression, intelligent cleanup
|
|
46
46
|
*/
|
|
47
47
|
export class ChatSessionManager {
|
|
48
|
+
static FILE_READ_CHUNK_SIZE = 10;
|
|
48
49
|
repoPath;
|
|
49
50
|
storageDir;
|
|
50
51
|
currentSession = null;
|
|
@@ -268,18 +269,32 @@ export class ChatSessionManager {
|
|
|
268
269
|
*/
|
|
269
270
|
async listSessions() {
|
|
270
271
|
const files = await this.fileAdapter.readdir(this.storageDir).catch(() => []);
|
|
272
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json'));
|
|
271
273
|
const sessions = [];
|
|
272
|
-
for (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
274
|
+
for (let i = 0; i < jsonFiles.length; i += ChatSessionManager.FILE_READ_CHUNK_SIZE) {
|
|
275
|
+
const chunk = jsonFiles.slice(i, i + ChatSessionManager.FILE_READ_CHUNK_SIZE);
|
|
276
|
+
const promises = chunk.map(async (file) => {
|
|
277
|
+
try {
|
|
278
|
+
const filePath = join(this.storageDir, file);
|
|
279
|
+
const data = await this.fileAdapter.readFile(filePath);
|
|
280
|
+
const session = JSON.parse(data);
|
|
281
|
+
return {
|
|
282
|
+
id: session.meta.id,
|
|
283
|
+
name: session.meta.name,
|
|
284
|
+
updatedAt: session.meta.updatedAt,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
getLogger().warn(`Failed to list session file ${file}: ${error}`);
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
282
291
|
});
|
|
292
|
+
const results = await Promise.all(promises);
|
|
293
|
+
for (const result of results) {
|
|
294
|
+
if (result) {
|
|
295
|
+
sessions.push(result);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
283
298
|
}
|
|
284
299
|
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
285
300
|
}
|
|
@@ -291,19 +306,26 @@ export class ChatSessionManager {
|
|
|
291
306
|
const analysis = this.pruningEngine.analyzeSessions(sessions);
|
|
292
307
|
let deleted = 0;
|
|
293
308
|
let archived = 0;
|
|
309
|
+
const CHUNK_SIZE = 10;
|
|
294
310
|
// Delete low-priority sessions
|
|
295
|
-
for (
|
|
296
|
-
|
|
297
|
-
|
|
311
|
+
for (let i = 0; i < analysis.sessionsToDelete.length; i += CHUNK_SIZE) {
|
|
312
|
+
const chunk = analysis.sessionsToDelete.slice(i, i + CHUNK_SIZE);
|
|
313
|
+
await Promise.all(chunk.map(async (sessionId) => {
|
|
314
|
+
await this.deleteSession(sessionId);
|
|
315
|
+
deleted++;
|
|
316
|
+
}));
|
|
298
317
|
}
|
|
299
318
|
// Archive medium-priority sessions
|
|
300
|
-
for (
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
319
|
+
for (let i = 0; i < analysis.sessionsToArchive.length; i += CHUNK_SIZE) {
|
|
320
|
+
const chunk = analysis.sessionsToArchive.slice(i, i + CHUNK_SIZE);
|
|
321
|
+
await Promise.all(chunk.map(async (sessionId) => {
|
|
322
|
+
const session = sessions.find((s) => s.meta.id === sessionId);
|
|
323
|
+
if (session) {
|
|
324
|
+
await this.archiveSession(session);
|
|
325
|
+
await this.deleteSession(sessionId);
|
|
326
|
+
archived++;
|
|
327
|
+
}
|
|
328
|
+
}));
|
|
307
329
|
}
|
|
308
330
|
return {
|
|
309
331
|
deleted,
|
|
@@ -323,22 +345,30 @@ export class ChatSessionManager {
|
|
|
323
345
|
*/
|
|
324
346
|
async loadAllSessions() {
|
|
325
347
|
const files = await this.fileAdapter.readdir(this.storageDir).catch(() => []);
|
|
348
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json'));
|
|
326
349
|
const sessions = [];
|
|
327
|
-
for (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
350
|
+
for (let i = 0; i < jsonFiles.length; i += ChatSessionManager.FILE_READ_CHUNK_SIZE) {
|
|
351
|
+
const chunk = jsonFiles.slice(i, i + ChatSessionManager.FILE_READ_CHUNK_SIZE);
|
|
352
|
+
const promises = chunk.map(async (file) => {
|
|
353
|
+
try {
|
|
354
|
+
const filePath = join(this.storageDir, file);
|
|
355
|
+
const data = await this.fileAdapter.readFile(filePath);
|
|
356
|
+
const session = JSON.parse(data);
|
|
357
|
+
session.meta.chatState = normalizeChatState(session.meta.chatState);
|
|
358
|
+
session.meta.artifactState = normalizeSessionArtifactState(session.meta.artifactState);
|
|
359
|
+
session.meta.replacementState = normalizeToolResultReplacementState(session.meta.replacementState);
|
|
360
|
+
return session;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
getLogger().warn(`Failed to load session file ${file}: ${error}`);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
const results = await Promise.all(promises);
|
|
368
|
+
for (const result of results) {
|
|
369
|
+
if (result) {
|
|
370
|
+
sessions.push(result);
|
|
371
|
+
}
|
|
342
372
|
}
|
|
343
373
|
}
|
|
344
374
|
return sessions;
|
|
@@ -13,6 +13,7 @@ import { planInitSpec, planReadSpec, planUpdateSpec } from './plan.js';
|
|
|
13
13
|
import { proposalApplySpec, executeProposalApply } from './proposal.js';
|
|
14
14
|
import { shellExecSpec, executeShellExec } from './shell.js';
|
|
15
15
|
import { verifyRunSpec, executeVerifyRun } from './verify.js';
|
|
16
|
+
import { workspaceInfoSpec, executeWorkspaceInfo } from './workspace.js';
|
|
16
17
|
/**
|
|
17
18
|
* Registers all builtin tools into the provided registry
|
|
18
19
|
*/
|
|
@@ -27,6 +28,10 @@ export function registerAllBuiltins(registry) {
|
|
|
27
28
|
...updateKnowledgeSpec,
|
|
28
29
|
executor: executeUpdateKnowledge,
|
|
29
30
|
});
|
|
31
|
+
registry.register({
|
|
32
|
+
...workspaceInfoSpec,
|
|
33
|
+
executor: executeWorkspaceInfo,
|
|
34
|
+
});
|
|
30
35
|
registry.register({
|
|
31
36
|
...proposalApplySpec,
|
|
32
37
|
executor: executeProposalApply,
|
|
@@ -125,5 +130,5 @@ export function registerAllBuiltins(registry) {
|
|
|
125
130
|
registry.register(planUpdateSpec);
|
|
126
131
|
registry.register(askUserSpec);
|
|
127
132
|
}
|
|
128
|
-
export { CodeSearchSpec, codeSearchExecutor, astDefsRefsSpec as codeAstSpec, executeAstDefsRefs as executeCodeAst, gitCatSpec, executeGitCat, gitStatusSpec, executeGitStatus, codeReadSpec, fsListSpec, executeFsList, fsReadFileSpec as fsReadSpec, executeFsReadFile as executeFsRead, updateKnowledgeSpec, executeUpdateKnowledge, astGrepSpec as codeSearchAstSpec, executeAstGrep as executeCodeSearchAst, verifyRunSpec as testRunSpec, executeVerifyRun as executeTestRun, gitDiffCheckSpec, executeGitDiffCheck, gitApplyCheckSpec, executeGitApplyCheck, benchmarkReportSpec, executeBenchmarkReport, sweBenchLoadInstanceSpec, executeSweBenchLoadInstance, sweBenchWritePredictionSpec, executeSweBenchWritePrediction, sweBenchSubmitPredictionsSpec, executeSweBenchSubmitPredictions, sweBenchGetReportSpec, executeSweBenchGetReport, };
|
|
133
|
+
export { CodeSearchSpec, codeSearchExecutor, astDefsRefsSpec as codeAstSpec, executeAstDefsRefs as executeCodeAst, gitCatSpec, executeGitCat, gitStatusSpec, executeGitStatus, codeReadSpec, fsListSpec, executeFsList, fsReadFileSpec as fsReadSpec, executeFsReadFile as executeFsRead, updateKnowledgeSpec, executeUpdateKnowledge, astGrepSpec as codeSearchAstSpec, executeAstGrep as executeCodeSearchAst, verifyRunSpec as testRunSpec, executeVerifyRun as executeTestRun, gitDiffCheckSpec, executeGitDiffCheck, gitApplyCheckSpec, executeGitApplyCheck, benchmarkReportSpec, executeBenchmarkReport, sweBenchLoadInstanceSpec, executeSweBenchLoadInstance, sweBenchWritePredictionSpec, executeSweBenchWritePrediction, sweBenchSubmitPredictionsSpec, executeSweBenchSubmitPredictions, sweBenchGetReportSpec, executeSweBenchGetReport, workspaceInfoSpec, executeWorkspaceInfo, };
|
|
129
134
|
//# sourceMappingURL=index.js.map
|
|
@@ -7,10 +7,7 @@ import { DecisionEngine, PlanBuilder } from '../../grizzco/dsl/DecisionEngine.js
|
|
|
7
7
|
import { StandardStrategy } from '../../grizzco/dsl/strategies.js';
|
|
8
8
|
import { Executor } from '../../grizzco/execution/Executor.js';
|
|
9
9
|
import { WorkerFactory } from '../../grizzco/execution/WorkerFactory.js';
|
|
10
|
-
import { CachedService } from '../../grizzco/services/CachedService.js';
|
|
11
|
-
import { GitConfigService } from '../../grizzco/services/implementations/default/GitConfigService.js';
|
|
12
10
|
import { MockLockService } from '../../grizzco/services/implementations/mock/MockLockService.js';
|
|
13
|
-
import { MockUserQuotaService } from '../../grizzco/services/implementations/mock/MockUserQuotaService.js';
|
|
14
11
|
import { registry } from '../../grizzco/services/registry.js';
|
|
15
12
|
import { normalizeDiff, validateDiff, convertDiffToShadowOperations } from '../../patch/diff.js';
|
|
16
13
|
import { getRejectionsDir } from '../../runtime/paths.js';
|
|
@@ -20,10 +17,6 @@ import { Phase } from '../../types/runtime.js';
|
|
|
20
17
|
function bootstrapRegistry() {
|
|
21
18
|
if (!registry.has('remote_lock'))
|
|
22
19
|
registry.register(new MockLockService());
|
|
23
|
-
if (!registry.has('user_quota'))
|
|
24
|
-
registry.register(new MockUserQuotaService());
|
|
25
|
-
if (!registry.has('git_config'))
|
|
26
|
-
registry.register(new CachedService(new GitConfigService()));
|
|
27
20
|
}
|
|
28
21
|
export const proposalApplySpec = {
|
|
29
22
|
name: 'proposal.apply',
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Phase } from '../../types/runtime.js';
|
|
3
|
+
import { detectWorkspaceCapabilities } from '../../workspace/capabilities.js';
|
|
4
|
+
const WorkspaceCapabilitiesOutputSchema = z.object({
|
|
5
|
+
root: z.string(),
|
|
6
|
+
capabilities: z.object({
|
|
7
|
+
git: z.object({
|
|
8
|
+
available: z.boolean(),
|
|
9
|
+
insideWorkTree: z.boolean(),
|
|
10
|
+
head: z.string().optional(),
|
|
11
|
+
reason: z.string().optional(),
|
|
12
|
+
}),
|
|
13
|
+
filesystem: z.object({
|
|
14
|
+
readable: z.boolean(),
|
|
15
|
+
writable: z.boolean(),
|
|
16
|
+
reason: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
}),
|
|
19
|
+
guidance: z.object({
|
|
20
|
+
useGitTools: z.boolean(),
|
|
21
|
+
useFilesystemReadTools: z.boolean(),
|
|
22
|
+
useFilesystemWriteTools: z.boolean(),
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
export const workspaceInfoSpec = {
|
|
26
|
+
name: 'workspace.info',
|
|
27
|
+
source: 'builtin',
|
|
28
|
+
intent: 'READ',
|
|
29
|
+
description: 'Report the current workspace root and available capabilities, including whether git tools are usable.',
|
|
30
|
+
riskLevel: 'low',
|
|
31
|
+
sideEffects: ['none'],
|
|
32
|
+
concurrency: 'parallel_ok',
|
|
33
|
+
inputSchema: z.object({}),
|
|
34
|
+
outputSchema: WorkspaceCapabilitiesOutputSchema,
|
|
35
|
+
allowedPhases: [
|
|
36
|
+
Phase.SLASH,
|
|
37
|
+
Phase.CONTEXT,
|
|
38
|
+
Phase.EXPLORE,
|
|
39
|
+
Phase.PLAN,
|
|
40
|
+
Phase.AUTOPILOT,
|
|
41
|
+
Phase.PATCH,
|
|
42
|
+
Phase.VERIFY,
|
|
43
|
+
Phase.SHRINK,
|
|
44
|
+
],
|
|
45
|
+
examples: [
|
|
46
|
+
{
|
|
47
|
+
description: 'Check whether git tools are available before calling git.status',
|
|
48
|
+
input: {},
|
|
49
|
+
output: {
|
|
50
|
+
root: '/workspace/project',
|
|
51
|
+
capabilities: {
|
|
52
|
+
git: { available: true, insideWorkTree: true, head: '<commit>' },
|
|
53
|
+
filesystem: { readable: true, writable: true },
|
|
54
|
+
},
|
|
55
|
+
guidance: {
|
|
56
|
+
useGitTools: true,
|
|
57
|
+
useFilesystemReadTools: true,
|
|
58
|
+
useFilesystemWriteTools: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
export async function executeWorkspaceInfo(_input, ctx) {
|
|
65
|
+
const capabilities = ctx.workspaceCapabilities ?? (await detectWorkspaceCapabilities(ctx.repoRoot));
|
|
66
|
+
return {
|
|
67
|
+
root: ctx.repoRoot,
|
|
68
|
+
capabilities,
|
|
69
|
+
guidance: {
|
|
70
|
+
useGitTools: capabilities.git.available && capabilities.git.insideWorkTree,
|
|
71
|
+
useFilesystemReadTools: capabilities.filesystem.readable,
|
|
72
|
+
useFilesystemWriteTools: capabilities.filesystem.writable,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -40,6 +40,7 @@ export class ToolDispatcher {
|
|
|
40
40
|
repoRoot: this.options.repoRoot,
|
|
41
41
|
persistenceRoot: this.options.persistenceRoot,
|
|
42
42
|
worktreeRoot: this.options.worktreeRoot,
|
|
43
|
+
workspaceCapabilities: this.options.workspaceCapabilities,
|
|
43
44
|
attemptId: this.options.attemptId,
|
|
44
45
|
dryRun: this.options.dryRun,
|
|
45
46
|
model: this.options.model,
|