@namzu/sdk 0.1.5-rc.1-fix → 0.1.5
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/CHANGELOG.md +25 -0
- package/README.md +279 -129
- package/dist/bridge/tools/connector/adapter.d.ts +2 -2
- package/dist/bridge/tools/connector/adapter.d.ts.map +1 -1
- package/dist/bridge/tools/connector/adapter.js +3 -1
- package/dist/bridge/tools/connector/adapter.js.map +1 -1
- package/dist/connector/BaseConnector.d.ts +2 -1
- package/dist/connector/BaseConnector.d.ts.map +1 -1
- package/dist/connector/BaseConnector.js.map +1 -1
- package/dist/connector/builtins/http.d.ts +1 -1
- package/dist/connector/builtins/http.d.ts.map +1 -1
- package/dist/connector/builtins/http.js +1 -1
- package/dist/connector/builtins/http.js.map +1 -1
- package/dist/connector/builtins/webhook.d.ts +1 -1
- package/dist/connector/builtins/webhook.d.ts.map +1 -1
- package/dist/connector/builtins/webhook.js +1 -1
- package/dist/connector/builtins/webhook.js.map +1 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +7 -0
- package/dist/contracts/index.js.map +1 -1
- package/dist/index.d.ts +5 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -23
- package/dist/index.js.map +1 -1
- package/dist/manager/connector/environment.d.ts +4 -4
- package/dist/manager/connector/environment.d.ts.map +1 -1
- package/dist/manager/connector/environment.js.map +1 -1
- package/dist/manager/connector/lifecycle.d.ts +2 -2
- package/dist/manager/connector/lifecycle.d.ts.map +1 -1
- package/dist/manager/connector/lifecycle.js.map +1 -1
- package/dist/manager/connector/tenant.d.ts +3 -3
- package/dist/manager/connector/tenant.d.ts.map +1 -1
- package/dist/manager/connector/tenant.js.map +1 -1
- package/dist/manager/index.d.ts +2 -1
- package/dist/manager/index.d.ts.map +1 -1
- package/dist/manager/index.js +2 -1
- package/dist/manager/index.js.map +1 -1
- package/dist/manager/run/emergency.d.ts.map +1 -1
- package/dist/manager/run/emergency.js +44 -12
- package/dist/manager/run/emergency.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +0 -2
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +0 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/plugin/__tests__/enable-contributions.test.d.ts +2 -0
- package/dist/plugin/__tests__/enable-contributions.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/enable-contributions.test.js +265 -0
- package/dist/plugin/__tests__/enable-contributions.test.js.map +1 -0
- package/dist/plugin/__tests__/lifecycle.test.js +30 -0
- package/dist/plugin/__tests__/lifecycle.test.js.map +1 -1
- package/dist/plugin/lifecycle.d.ts +4 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +157 -49
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/rag/vector-store.d.ts +2 -2
- package/dist/rag/vector-store.d.ts.map +1 -1
- package/dist/rag/vector-store.js.map +1 -1
- package/dist/registry/ManagedRegistry.d.ts +6 -0
- package/dist/registry/ManagedRegistry.d.ts.map +1 -1
- package/dist/registry/ManagedRegistry.js +9 -3
- package/dist/registry/ManagedRegistry.js.map +1 -1
- package/dist/registry/agent/definitions.d.ts +2 -7
- package/dist/registry/agent/definitions.d.ts.map +1 -1
- package/dist/registry/agent/definitions.js +3 -41
- package/dist/registry/agent/definitions.js.map +1 -1
- package/dist/registry/connector/definitions.d.ts +2 -7
- package/dist/registry/connector/definitions.d.ts.map +1 -1
- package/dist/registry/connector/definitions.js +3 -41
- package/dist/registry/connector/definitions.js.map +1 -1
- package/dist/registry/connector/scoped.d.ts +5 -4
- package/dist/registry/connector/scoped.d.ts.map +1 -1
- package/dist/registry/connector/scoped.js.map +1 -1
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -0
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +2 -4
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +4 -21
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/run/index.d.ts +4 -4
- package/dist/run/index.d.ts.map +1 -1
- package/dist/run/index.js +3 -3
- package/dist/run/index.js.map +1 -1
- package/dist/run/reporter.d.ts +0 -2
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +0 -1
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/executor-plugin-hooks.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/executor-plugin-hooks.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/executor-plugin-hooks.test.js +236 -0
- package/dist/runtime/query/__tests__/executor-plugin-hooks.test.js.map +1 -0
- package/dist/runtime/query/__tests__/plugin-hooks.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/plugin-hooks.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/plugin-hooks.test.js +21 -0
- package/dist/runtime/query/__tests__/plugin-hooks.test.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +6 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +110 -6
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +8 -6
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +13 -16
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.d.ts +11 -0
- package/dist/runtime/query/plugin-hooks.d.ts.map +1 -0
- package/dist/runtime/query/plugin-hooks.js +28 -0
- package/dist/runtime/query/plugin-hooks.js.map +1 -0
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/store/index.d.ts +5 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +4 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/run/disk.d.ts +0 -2
- package/dist/store/run/disk.d.ts.map +1 -1
- package/dist/store/run/disk.js +0 -1
- package/dist/store/run/disk.js.map +1 -1
- package/dist/store/task/__tests__/disk-concurrency.test.d.ts +2 -0
- package/dist/store/task/__tests__/disk-concurrency.test.d.ts.map +1 -0
- package/dist/store/task/__tests__/disk-concurrency.test.js +91 -0
- package/dist/store/task/__tests__/disk-concurrency.test.js.map +1 -0
- package/dist/store/task/disk.d.ts +6 -0
- package/dist/store/task/disk.d.ts.map +1 -1
- package/dist/store/task/disk.js +150 -36
- package/dist/store/task/disk.js.map +1 -1
- package/dist/types/connector/core.d.ts +2 -2
- package/dist/types/connector/core.d.ts.map +1 -1
- package/dist/types/connector/definition.d.ts +7 -7
- package/dist/types/connector/definition.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +4 -4
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/connector/scope.d.ts +3 -2
- package/dist/types/connector/scope.d.ts.map +1 -1
- package/dist/types/connector/scope.js.map +1 -1
- package/dist/types/connector/tenant.d.ts +4 -4
- package/dist/types/connector/tenant.d.ts.map +1 -1
- package/dist/types/plugin/index.d.ts +4 -21
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +1 -10
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/rag/knowledge-base.d.ts +2 -2
- package/dist/types/rag/knowledge-base.d.ts.map +1 -1
- package/dist/types/rag/scope.d.ts +2 -1
- package/dist/types/rag/scope.d.ts.map +1 -1
- package/dist/types/rag/storage.d.ts +3 -3
- package/dist/types/rag/storage.d.ts.map +1 -1
- package/dist/types/rag/vector.d.ts +3 -3
- package/dist/types/rag/vector.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +0 -6
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/vault/InMemoryCredentialVault.d.ts +3 -3
- package/dist/vault/InMemoryCredentialVault.d.ts.map +1 -1
- package/dist/vault/InMemoryCredentialVault.js.map +1 -1
- package/package.json +1 -1
- package/src/bridge/tools/connector/adapter.ts +5 -3
- package/src/connector/BaseConnector.ts +2 -1
- package/src/connector/builtins/http.ts +1 -1
- package/src/connector/builtins/webhook.ts +1 -1
- package/src/contracts/api.ts +3 -0
- package/src/contracts/index.ts +10 -0
- package/src/index.ts +46 -41
- package/src/manager/connector/environment.ts +5 -5
- package/src/manager/connector/lifecycle.ts +2 -2
- package/src/manager/connector/tenant.ts +8 -3
- package/src/manager/index.ts +2 -1
- package/src/manager/run/emergency.ts +45 -16
- package/src/manager/run/persistence.ts +0 -4
- package/src/plugin/__tests__/enable-contributions.test.ts +295 -0
- package/src/plugin/__tests__/lifecycle.test.ts +44 -0
- package/src/plugin/lifecycle.ts +186 -57
- package/src/rag/vector-store.ts +2 -2
- package/src/registry/ManagedRegistry.ts +14 -3
- package/src/registry/agent/definitions.ts +3 -53
- package/src/registry/connector/definitions.ts +3 -53
- package/src/registry/connector/scoped.ts +7 -6
- package/src/registry/index.ts +1 -0
- package/src/registry/tool/execute.ts +4 -22
- package/src/run/index.ts +4 -4
- package/src/run/reporter.ts +0 -4
- package/src/runtime/query/__tests__/executor-plugin-hooks.test.ts +305 -0
- package/src/runtime/query/__tests__/plugin-hooks.test.ts +28 -0
- package/src/runtime/query/executor.ts +148 -7
- package/src/runtime/query/index.ts +16 -6
- package/src/runtime/query/iteration/index.ts +29 -16
- package/src/runtime/query/plugin-hooks.ts +34 -0
- package/src/runtime/query/tooling.ts +3 -0
- package/src/store/index.ts +6 -1
- package/src/store/run/disk.ts +0 -4
- package/src/store/task/__tests__/disk-concurrency.test.ts +118 -0
- package/src/store/task/disk.ts +150 -37
- package/src/types/connector/core.ts +2 -2
- package/src/types/connector/definition.ts +7 -7
- package/src/types/connector/mcp.ts +4 -4
- package/src/types/connector/scope.ts +3 -2
- package/src/types/connector/tenant.ts +4 -4
- package/src/types/plugin/index.ts +0 -14
- package/src/types/rag/knowledge-base.ts +2 -2
- package/src/types/rag/scope.ts +3 -1
- package/src/types/rag/storage.ts +3 -3
- package/src/types/rag/vector.ts +3 -3
- package/src/types/run/config.ts +0 -9
- package/src/vault/InMemoryCredentialVault.ts +3 -3
- package/dist/manager/agent/index.d.ts +0 -2
- package/dist/manager/agent/index.d.ts.map +0 -1
- package/dist/manager/agent/index.js +0 -2
- package/dist/manager/agent/index.js.map +0 -1
- package/dist/registry/agent/index.d.ts +0 -2
- package/dist/registry/agent/index.d.ts.map +0 -1
- package/dist/registry/agent/index.js +0 -2
- package/dist/registry/agent/index.js.map +0 -1
- package/dist/router/index.d.ts +0 -2
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/index.js +0 -2
- package/dist/router/index.js.map +0 -1
- package/src/manager/agent/index.ts +0 -1
- package/src/registry/agent/index.ts +0 -1
- package/src/router/index.ts +0 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { extractFromToolCall, extractFromToolResult } from '../../compaction/extractor.js'
|
|
2
2
|
import type { WorkingStateManager } from '../../compaction/manager.js'
|
|
3
|
+
import type { PluginLifecycleManager } from '../../plugin/lifecycle.js'
|
|
3
4
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
4
5
|
import type { RunId } from '../../types/ids/index.js'
|
|
5
6
|
import type { InvocationState } from '../../types/invocation/index.js'
|
|
6
7
|
import { type Message, createToolMessage } from '../../types/message/index.js'
|
|
7
8
|
import type { PermissionMode } from '../../types/permission/index.js'
|
|
9
|
+
import type { PluginHookResult } from '../../types/plugin/index.js'
|
|
8
10
|
import type { ChatCompletionResponse } from '../../types/provider/index.js'
|
|
9
11
|
import type { RunEvent } from '../../types/run/index.js'
|
|
10
12
|
import type { Sandbox } from '../../types/sandbox/index.js'
|
|
11
|
-
import type { ToolContext, ToolRegistryContract } from '../../types/tool/index.js'
|
|
13
|
+
import type { ToolContext, ToolRegistryContract, ToolResult } from '../../types/tool/index.js'
|
|
12
14
|
import type { Logger } from '../../utils/logger.js'
|
|
13
15
|
import { compressShellOutput } from '../../utils/shell-compress.js'
|
|
14
16
|
|
|
@@ -23,8 +25,14 @@ export interface ToolExecutorConfig {
|
|
|
23
25
|
abortSignal: AbortSignal
|
|
24
26
|
sandbox?: Sandbox
|
|
25
27
|
invocationState?: InvocationState
|
|
28
|
+
pluginManager?: PluginLifecycleManager
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
type PreToolHookOutcome =
|
|
32
|
+
| { kind: 'continue'; input: unknown }
|
|
33
|
+
| { kind: 'skip'; input: unknown; output: string }
|
|
34
|
+
| { kind: 'error'; input: unknown; output: string }
|
|
35
|
+
|
|
28
36
|
export interface ToolExecutionBatch {
|
|
29
37
|
messages: Message[]
|
|
30
38
|
results: Array<{ toolCallId: string; output: string }>
|
|
@@ -118,6 +126,12 @@ export class ToolExecutor {
|
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
|
|
129
|
+
const preOutcome = await this.runPreToolHook(toolName, input)
|
|
130
|
+
if (preOutcome.kind === 'skip' || preOutcome.kind === 'error') {
|
|
131
|
+
return this.recordSyntheticHookOutcome(toolCall.id, toolName, preOutcome.input, preOutcome)
|
|
132
|
+
}
|
|
133
|
+
input = preOutcome.input
|
|
134
|
+
|
|
121
135
|
const activity = this.activityStore.create({
|
|
122
136
|
type: 'tool_call',
|
|
123
137
|
description: toolName,
|
|
@@ -137,7 +151,7 @@ export class ToolExecutor {
|
|
|
137
151
|
})
|
|
138
152
|
|
|
139
153
|
if (this.workingStateManager) {
|
|
140
|
-
extractFromToolCall(this.workingStateManager, toolName,
|
|
154
|
+
extractFromToolCall(this.workingStateManager, toolName, JSON.stringify(input))
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
const startMs = Date.now()
|
|
@@ -148,10 +162,17 @@ export class ToolExecutor {
|
|
|
148
162
|
? result.output
|
|
149
163
|
: `Error: ${result.error ?? 'Tool execution failed'}`
|
|
150
164
|
|
|
151
|
-
|
|
165
|
+
let output = result.success ? this.maybeCompress(toolName, rawOutput) : rawOutput
|
|
166
|
+
|
|
167
|
+
const postOverride = await this.runPostToolHook(toolName, input, result)
|
|
168
|
+
if (postOverride !== null) {
|
|
169
|
+
output = postOverride
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const effectiveIsError = !result.success || postOverride !== null
|
|
152
173
|
|
|
153
174
|
if (this.workingStateManager) {
|
|
154
|
-
extractFromToolResult(this.workingStateManager, toolName, output,
|
|
175
|
+
extractFromToolResult(this.workingStateManager, toolName, output, effectiveIsError)
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
if (result.success) {
|
|
@@ -171,10 +192,10 @@ export class ToolExecutor {
|
|
|
171
192
|
}
|
|
172
193
|
|
|
173
194
|
if (activity) {
|
|
174
|
-
if (
|
|
175
|
-
this.activityStore.
|
|
195
|
+
if (effectiveIsError) {
|
|
196
|
+
this.activityStore.fail(activity.id, output)
|
|
176
197
|
} else {
|
|
177
|
-
this.activityStore.
|
|
198
|
+
this.activityStore.complete(activity.id, output)
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
201
|
|
|
@@ -188,6 +209,126 @@ export class ToolExecutor {
|
|
|
188
209
|
return { toolCallId: toolCall.id, output }
|
|
189
210
|
}
|
|
190
211
|
|
|
212
|
+
private async runPreToolHook(toolName: string, input: unknown): Promise<PreToolHookOutcome> {
|
|
213
|
+
if (!this.config.pluginManager) return { kind: 'continue', input }
|
|
214
|
+
const results = await this.config.pluginManager.executeHooks(
|
|
215
|
+
'pre_tool_use',
|
|
216
|
+
{ runId: this.config.runId, toolName, toolInput: input },
|
|
217
|
+
this.emitEvent,
|
|
218
|
+
)
|
|
219
|
+
return this.interpretPreToolResults(toolName, input, results)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private interpretPreToolResults(
|
|
223
|
+
toolName: string,
|
|
224
|
+
initialInput: unknown,
|
|
225
|
+
results: readonly PluginHookResult[],
|
|
226
|
+
): PreToolHookOutcome {
|
|
227
|
+
let currentInput = initialInput
|
|
228
|
+
for (const result of results) {
|
|
229
|
+
switch (result.action) {
|
|
230
|
+
case 'continue':
|
|
231
|
+
continue
|
|
232
|
+
case 'modify':
|
|
233
|
+
currentInput = result.input
|
|
234
|
+
continue
|
|
235
|
+
case 'skip':
|
|
236
|
+
return {
|
|
237
|
+
kind: 'skip',
|
|
238
|
+
input: currentInput,
|
|
239
|
+
output: `Tool ${toolName} skipped by plugin: ${result.reason}`,
|
|
240
|
+
}
|
|
241
|
+
case 'error':
|
|
242
|
+
return {
|
|
243
|
+
kind: 'error',
|
|
244
|
+
input: currentInput,
|
|
245
|
+
output: `Error: ${result.message}`,
|
|
246
|
+
}
|
|
247
|
+
case 'retry':
|
|
248
|
+
case 'resume':
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Plugin hook pre_tool_use returned unsupported action '${result.action}' for tool ${toolName}`,
|
|
251
|
+
)
|
|
252
|
+
default: {
|
|
253
|
+
const _exhaustive: never = result
|
|
254
|
+
throw new Error(`Unknown PluginHookResult: ${JSON.stringify(_exhaustive)}`)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { kind: 'continue', input: currentInput }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async runPostToolHook(
|
|
262
|
+
toolName: string,
|
|
263
|
+
input: unknown,
|
|
264
|
+
toolResult: ToolResult,
|
|
265
|
+
): Promise<string | null> {
|
|
266
|
+
if (!this.config.pluginManager) return null
|
|
267
|
+
const results = await this.config.pluginManager.executeHooks(
|
|
268
|
+
'post_tool_use',
|
|
269
|
+
{ runId: this.config.runId, toolName, toolInput: input, toolResult },
|
|
270
|
+
this.emitEvent,
|
|
271
|
+
)
|
|
272
|
+
let override: string | null = null
|
|
273
|
+
for (const result of results) {
|
|
274
|
+
switch (result.action) {
|
|
275
|
+
case 'continue':
|
|
276
|
+
continue
|
|
277
|
+
case 'error':
|
|
278
|
+
override = `Error: ${result.message}`
|
|
279
|
+
continue
|
|
280
|
+
case 'skip':
|
|
281
|
+
case 'modify':
|
|
282
|
+
case 'retry':
|
|
283
|
+
case 'resume':
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Plugin hook post_tool_use returned unsupported action '${result.action}' for tool ${toolName}`,
|
|
286
|
+
)
|
|
287
|
+
default: {
|
|
288
|
+
const _exhaustive: never = result
|
|
289
|
+
throw new Error(`Unknown PluginHookResult: ${JSON.stringify(_exhaustive)}`)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return override
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async recordSyntheticHookOutcome(
|
|
297
|
+
toolCallId: string,
|
|
298
|
+
toolName: string,
|
|
299
|
+
input: unknown,
|
|
300
|
+
outcome: { kind: 'skip' | 'error'; output: string },
|
|
301
|
+
): Promise<{ toolCallId: string; output: string }> {
|
|
302
|
+
const activity = this.activityStore.create({
|
|
303
|
+
type: 'tool_call',
|
|
304
|
+
description: toolName,
|
|
305
|
+
input,
|
|
306
|
+
toolName,
|
|
307
|
+
toolCallId,
|
|
308
|
+
})
|
|
309
|
+
if (activity) {
|
|
310
|
+
this.activityStore.start(activity.id)
|
|
311
|
+
if (outcome.kind === 'skip') {
|
|
312
|
+
this.activityStore.complete(activity.id, outcome.output)
|
|
313
|
+
} else {
|
|
314
|
+
this.activityStore.fail(activity.id, outcome.output)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await this.emitEvent({
|
|
318
|
+
type: 'tool_executing',
|
|
319
|
+
runId: this.config.runId,
|
|
320
|
+
toolName,
|
|
321
|
+
input,
|
|
322
|
+
})
|
|
323
|
+
await this.emitEvent({
|
|
324
|
+
type: 'tool_completed',
|
|
325
|
+
runId: this.config.runId,
|
|
326
|
+
toolName,
|
|
327
|
+
result: outcome.output,
|
|
328
|
+
})
|
|
329
|
+
return { toolCallId, output: outcome.output }
|
|
330
|
+
}
|
|
331
|
+
|
|
191
332
|
private maybeCompress(toolName: string, output: string): string {
|
|
192
333
|
const tool = this.config.tools.get(toolName)
|
|
193
334
|
if (!tool || tool.category !== 'shell') {
|
|
@@ -40,6 +40,7 @@ import { RunContextFactory } from './context.js'
|
|
|
40
40
|
import { EventTranslator } from './events.js'
|
|
41
41
|
import { GuardCoordinator } from './guard.js'
|
|
42
42
|
import { IterationOrchestrator } from './iteration/index.js'
|
|
43
|
+
import { applyLifecycleHookResults } from './plugin-hooks.js'
|
|
43
44
|
import { PromptBuilder } from './prompt.js'
|
|
44
45
|
import type { PromptSegments } from './prompt.js'
|
|
45
46
|
import { ResultAssembler } from './result.js'
|
|
@@ -193,6 +194,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Agen
|
|
|
193
194
|
env: params.runConfig.env ?? {},
|
|
194
195
|
abortSignal: ctx.abortController.signal,
|
|
195
196
|
invocationState: params.invocationState,
|
|
197
|
+
pluginManager: params.pluginManager,
|
|
196
198
|
},
|
|
197
199
|
ctx.activityStore,
|
|
198
200
|
eventTranslator.emitEvent,
|
|
@@ -397,9 +399,13 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Agen
|
|
|
397
399
|
yield* eventTranslator.drainPending()
|
|
398
400
|
|
|
399
401
|
if (params.pluginManager) {
|
|
400
|
-
await params.pluginManager.executeHooks(
|
|
401
|
-
|
|
402
|
-
|
|
402
|
+
const hookResults = await params.pluginManager.executeHooks(
|
|
403
|
+
'run_start',
|
|
404
|
+
{ runId: ctx.runId },
|
|
405
|
+
eventTranslator.emitEvent,
|
|
406
|
+
)
|
|
407
|
+
applyLifecycleHookResults('run_start', hookResults)
|
|
408
|
+
yield* eventTranslator.drainPending()
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
// --- Sandbox lifecycle: create before iteration loop ---
|
|
@@ -429,9 +435,13 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Agen
|
|
|
429
435
|
yield* iterationOrchestrator.runLoop()
|
|
430
436
|
|
|
431
437
|
if (params.pluginManager) {
|
|
432
|
-
await params.pluginManager.executeHooks(
|
|
433
|
-
|
|
434
|
-
|
|
438
|
+
const hookResults = await params.pluginManager.executeHooks(
|
|
439
|
+
'run_end',
|
|
440
|
+
{ runId: ctx.runId },
|
|
441
|
+
eventTranslator.emitEvent,
|
|
442
|
+
)
|
|
443
|
+
applyLifecycleHookResults('run_end', hookResults)
|
|
444
|
+
yield* eventTranslator.drainPending()
|
|
435
445
|
}
|
|
436
446
|
|
|
437
447
|
yield* resultAssembler.completeRun(rootSpan)
|
|
@@ -19,6 +19,7 @@ import type { CheckpointManager } from '../checkpoint.js'
|
|
|
19
19
|
import type { EmitEvent } from '../events.js'
|
|
20
20
|
import type { ToolExecutor } from '../executor.js'
|
|
21
21
|
import type { GuardCoordinator } from '../guard.js'
|
|
22
|
+
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
22
23
|
import { runAdvisoryPhase } from './phases/advisory.js'
|
|
23
24
|
import { runIterationCheckpoint } from './phases/checkpoint.js'
|
|
24
25
|
import { runCompactionCheck } from './phases/compaction.js'
|
|
@@ -175,10 +176,13 @@ export class IterationOrchestrator {
|
|
|
175
176
|
|
|
176
177
|
try {
|
|
177
178
|
if (this.ctx.pluginManager) {
|
|
178
|
-
await this.ctx.pluginManager.executeHooks(
|
|
179
|
-
|
|
180
|
-
iteration: iterationNum,
|
|
181
|
-
|
|
179
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
180
|
+
'iteration_start',
|
|
181
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
182
|
+
this.ctx.emitEvent,
|
|
183
|
+
)
|
|
184
|
+
applyLifecycleHookResults('iteration_start', hookResults)
|
|
185
|
+
yield* this.ctx.drainPending()
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
if (this.ctx.pendingNotifications.length > 0) {
|
|
@@ -201,10 +205,13 @@ export class IterationOrchestrator {
|
|
|
201
205
|
: runMgr.messages
|
|
202
206
|
|
|
203
207
|
if (this.ctx.pluginManager) {
|
|
204
|
-
await this.ctx.pluginManager.executeHooks(
|
|
205
|
-
|
|
206
|
-
iteration: iterationNum,
|
|
207
|
-
|
|
208
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
209
|
+
'pre_llm_call',
|
|
210
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
211
|
+
this.ctx.emitEvent,
|
|
212
|
+
)
|
|
213
|
+
applyLifecycleHookResults('pre_llm_call', hookResults)
|
|
214
|
+
yield* this.ctx.drainPending()
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
const response = await this.ctx.provider.chat({
|
|
@@ -219,10 +226,13 @@ export class IterationOrchestrator {
|
|
|
219
226
|
runMgr.accumulateUsage(response.usage)
|
|
220
227
|
|
|
221
228
|
if (this.ctx.pluginManager) {
|
|
222
|
-
await this.ctx.pluginManager.executeHooks(
|
|
223
|
-
|
|
224
|
-
iteration: iterationNum,
|
|
225
|
-
|
|
229
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
230
|
+
'post_llm_call',
|
|
231
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
232
|
+
this.ctx.emitEvent,
|
|
233
|
+
)
|
|
234
|
+
applyLifecycleHookResults('post_llm_call', hookResults)
|
|
235
|
+
yield* this.ctx.drainPending()
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
this.ctx.log.debug('LLM response received', {
|
|
@@ -357,10 +367,13 @@ export class IterationOrchestrator {
|
|
|
357
367
|
await runAdvisoryPhase(this.ctx, iterationNum, response)
|
|
358
368
|
|
|
359
369
|
if (this.ctx.pluginManager) {
|
|
360
|
-
await this.ctx.pluginManager.executeHooks(
|
|
361
|
-
|
|
362
|
-
iteration: iterationNum,
|
|
363
|
-
|
|
370
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
371
|
+
'iteration_end',
|
|
372
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
373
|
+
this.ctx.emitEvent,
|
|
374
|
+
)
|
|
375
|
+
applyLifecycleHookResults('iteration_end', hookResults)
|
|
376
|
+
yield* this.ctx.drainPending()
|
|
364
377
|
}
|
|
365
378
|
|
|
366
379
|
await this.ctx.emitEvent({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PluginHookEvent, PluginHookResult } from '../../types/plugin/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interpret hook results from non-tool lifecycle events (run_start, run_end,
|
|
5
|
+
* iteration_start, iteration_end, pre_llm_call, post_llm_call). These contexts
|
|
6
|
+
* carry no mutable payload, so only `continue` and `error` are meaningful.
|
|
7
|
+
*
|
|
8
|
+
* Throws on `error` (propagated to run-failure path) or on action misuse
|
|
9
|
+
* (skip / modify / retry / resume have no defined contract here).
|
|
10
|
+
*/
|
|
11
|
+
export function applyLifecycleHookResults(
|
|
12
|
+
event: PluginHookEvent,
|
|
13
|
+
results: readonly PluginHookResult[],
|
|
14
|
+
): void {
|
|
15
|
+
for (const result of results) {
|
|
16
|
+
switch (result.action) {
|
|
17
|
+
case 'continue':
|
|
18
|
+
continue
|
|
19
|
+
case 'error':
|
|
20
|
+
throw new Error(`Plugin hook ${event} reported error: ${result.message}`)
|
|
21
|
+
case 'skip':
|
|
22
|
+
case 'modify':
|
|
23
|
+
case 'retry':
|
|
24
|
+
case 'resume':
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Plugin hook ${event} returned unsupported action '${result.action}' for a lifecycle event`,
|
|
27
|
+
)
|
|
28
|
+
default: {
|
|
29
|
+
const _exhaustive: never = result
|
|
30
|
+
throw new Error(`Unknown PluginHookResult: ${JSON.stringify(_exhaustive)}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PluginLifecycleManager } from '../../plugin/lifecycle.js'
|
|
1
2
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
2
3
|
import type { RunId } from '../../types/ids/index.js'
|
|
3
4
|
import type { InvocationState } from '../../types/invocation/index.js'
|
|
@@ -17,6 +18,7 @@ export interface ToolingBootstrapConfig {
|
|
|
17
18
|
env: Record<string, string>
|
|
18
19
|
abortSignal: AbortSignal
|
|
19
20
|
invocationState?: InvocationState
|
|
21
|
+
pluginManager?: PluginLifecycleManager
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export class ToolingBootstrap {
|
|
@@ -35,6 +37,7 @@ export class ToolingBootstrap {
|
|
|
35
37
|
env: config.env,
|
|
36
38
|
abortSignal: config.abortSignal,
|
|
37
39
|
invocationState: config.invocationState,
|
|
40
|
+
pluginManager: config.pluginManager,
|
|
38
41
|
},
|
|
39
42
|
activityStore,
|
|
40
43
|
emitEvent,
|
package/src/store/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { InMemoryStore } from './InMemoryStore.js'
|
|
2
2
|
export type { Identifiable, Timestamped } from './InMemoryStore.js'
|
|
3
3
|
|
|
4
|
-
export { RunDiskStore
|
|
4
|
+
export { RunDiskStore } from './run/disk.js'
|
|
5
5
|
|
|
6
6
|
export { ActivityStore } from './activity/memory.js'
|
|
7
7
|
export type { ActivityEvent, ActivityEventListener } from './activity/memory.js'
|
|
@@ -12,3 +12,8 @@ export type { DiskTaskStoreConfig } from './task/disk.js'
|
|
|
12
12
|
|
|
13
13
|
export { InMemoryConversationStore } from './conversation/memory.js'
|
|
14
14
|
export type { InMemoryConversationStoreConfig } from './conversation/memory.js'
|
|
15
|
+
|
|
16
|
+
export { InMemoryMemoryIndex } from './memory/index.js'
|
|
17
|
+
export { InMemoryMemoryStore } from './memory/memory.js'
|
|
18
|
+
export { DiskMemoryStore } from './memory/disk.js'
|
|
19
|
+
export type { DiskMemoryStoreConfig } from './memory/disk.js'
|
package/src/store/run/disk.ts
CHANGED
|
@@ -217,7 +217,3 @@ async function atomicWriteJson(filePath: string, value: unknown): Promise<void>
|
|
|
217
217
|
function isFileNotFound(err: unknown): boolean {
|
|
218
218
|
return typeof err === 'object' && err !== null && (err as NodeJS.ErrnoException).code === 'ENOENT'
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
export const SessionStore = RunDiskStore
|
|
222
|
-
|
|
223
|
-
export type SessionStore = RunDiskStore
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import type { RunId } from '../../../types/ids/index.js'
|
|
6
|
+
import { generateRunId } from '../../../utils/id.js'
|
|
7
|
+
import { DiskTaskStore } from '../disk.js'
|
|
8
|
+
|
|
9
|
+
describe('DiskTaskStore — concurrency regressions', () => {
|
|
10
|
+
let baseDir: string
|
|
11
|
+
let runId: RunId
|
|
12
|
+
let store: DiskTaskStore
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
baseDir = mkdtempSync(join(tmpdir(), 'namzu-task-concurrency-'))
|
|
16
|
+
runId = generateRunId()
|
|
17
|
+
store = new DiskTaskStore({ baseDir, defaultRunId: runId })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
rmSync(baseDir, { recursive: true, force: true })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('does not deadlock when two deletes race on mutually-referencing tasks', async () => {
|
|
25
|
+
const a = await store.create({ runId, subject: 'A' })
|
|
26
|
+
const b = await store.create({ runId, subject: 'B' })
|
|
27
|
+
|
|
28
|
+
// Establish bidirectional edge: A blocks B AND B blocks A.
|
|
29
|
+
// (Nonsensical semantically, but the store allows it and the lock logic
|
|
30
|
+
// must not deadlock.)
|
|
31
|
+
await store.block(a.id, b.id)
|
|
32
|
+
await store.block(b.id, a.id)
|
|
33
|
+
|
|
34
|
+
// Race the two deletes. The pre-fix implementation (lock this → iterate
|
|
35
|
+
// related → lock each) could acquire A→B from one call and B→A from the
|
|
36
|
+
// other, deadlocking. withLocks() sorts IDs canonically so both calls
|
|
37
|
+
// acquire [A, B] in the same order.
|
|
38
|
+
const withTimeout = <T>(p: Promise<T>, ms: number): Promise<T> =>
|
|
39
|
+
Promise.race([
|
|
40
|
+
p,
|
|
41
|
+
new Promise<never>((_resolve, reject) =>
|
|
42
|
+
setTimeout(() => reject(new Error('timeout — likely deadlocked')), ms),
|
|
43
|
+
),
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
const [resA, resB] = await withTimeout(
|
|
47
|
+
Promise.all([store.delete(a.id), store.delete(b.id)]),
|
|
48
|
+
2000,
|
|
49
|
+
)
|
|
50
|
+
expect(resA).toBe(true)
|
|
51
|
+
expect(resB).toBe(true)
|
|
52
|
+
expect(await store.get(a.id)).toBeUndefined()
|
|
53
|
+
expect(await store.get(b.id)).toBeUndefined()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('serializes concurrent same-ID updates (withLock race regression)', async () => {
|
|
57
|
+
const task = await store.create({ runId, subject: 'shared' })
|
|
58
|
+
|
|
59
|
+
// update()'s metadata merge does `{ ...task.metadata, ...updates.metadata }`
|
|
60
|
+
// inside withLock. If withLock serializes correctly, every update's key
|
|
61
|
+
// survives into the final metadata (each sees the latest merged state).
|
|
62
|
+
// If withLock had the race bug, two concurrent updates would both read
|
|
63
|
+
// the SAME snapshot, each add their one key, and one update's key would
|
|
64
|
+
// be overwritten when the second committed — yielding < N keys in the
|
|
65
|
+
// final metadata.
|
|
66
|
+
const n = 20
|
|
67
|
+
await Promise.all(
|
|
68
|
+
Array.from({ length: n }, (_v, i) =>
|
|
69
|
+
store.update(task.id, { metadata: { [`k${i}`]: true } }),
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const final = await store.get(task.id)
|
|
74
|
+
const keys = Object.keys(final?.metadata ?? {})
|
|
75
|
+
expect(keys).toHaveLength(n)
|
|
76
|
+
for (let i = 0; i < n; i++) {
|
|
77
|
+
expect(final?.metadata?.[`k${i}`]).toBe(true)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('establishes bidirectional edge atomically under create()', async () => {
|
|
82
|
+
const blocker = await store.create({ runId, subject: 'blocker' })
|
|
83
|
+
|
|
84
|
+
// Race: create a child with blockedBy=[blocker] while concurrently
|
|
85
|
+
// deleting the blocker. Either outcome is acceptable (child created
|
|
86
|
+
// and blocker still present in its blocks list, OR blocker gone and
|
|
87
|
+
// child created with dangling reference), but we must NEVER see
|
|
88
|
+
// blocker still present WITHOUT having child in its blocks list.
|
|
89
|
+
const tasks = await Promise.all([
|
|
90
|
+
store.create({ runId, subject: 'child', blockedBy: [blocker.id] }),
|
|
91
|
+
// No delete here — keep the create edge test focused. The point is
|
|
92
|
+
// that after create() resolves, the blocker's blocks list contains
|
|
93
|
+
// the new task ID atomically.
|
|
94
|
+
])
|
|
95
|
+
const child = tasks[0]
|
|
96
|
+
|
|
97
|
+
const blockerAfter = await store.get(blocker.id)
|
|
98
|
+
expect(blockerAfter).toBeDefined()
|
|
99
|
+
expect(blockerAfter?.blocks).toContain(child.id)
|
|
100
|
+
expect(child.blockedBy).toContain(blocker.id)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('block() skips gracefully when one task disappeared before lock acquired', async () => {
|
|
104
|
+
const a = await store.create({ runId, subject: 'A' })
|
|
105
|
+
const b = await store.create({ runId, subject: 'B' })
|
|
106
|
+
|
|
107
|
+
// Delete B, then try to block a → b. The findTask pre-check passes for a
|
|
108
|
+
// but fails for b, so block() returns early (silent no-op per existing
|
|
109
|
+
// contract).
|
|
110
|
+
await store.delete(b.id)
|
|
111
|
+
|
|
112
|
+
// block() should not throw and A's blocks list should remain empty.
|
|
113
|
+
await store.block(a.id, b.id)
|
|
114
|
+
|
|
115
|
+
const aAfter = await store.get(a.id)
|
|
116
|
+
expect(aAfter?.blocks).not.toContain(b.id)
|
|
117
|
+
})
|
|
118
|
+
})
|