macro-agent 0.0.17 → 0.1.1
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/.claude/settings.local.json +3 -1
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +16 -14
- package/README.md +11 -29
- package/dist/acp/macro-agent.d.ts +17 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +183 -55
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +32 -1
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +65 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +464 -183
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +37 -6
- package/dist/api/server.js.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token.d.ts +41 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +73 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli/acp.d.ts +2 -23
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +127 -61
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +147 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +268 -181
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/parse-args.d.ts +20 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/stable-instance-id.d.ts +8 -0
- package/dist/cli/stable-instance-id.d.ts.map +1 -0
- package/dist/cli/stable-instance-id.js +14 -0
- package/dist/cli/stable-instance-id.js.map +1 -0
- package/dist/config/project-config.d.ts +74 -7
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +123 -20
- package/dist/config/project-config.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts +23 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +482 -55
- package/dist/map/adapter/acp-over-map.js.map +1 -1
- package/dist/map/adapter/connection-manager.d.ts.map +1 -1
- package/dist/map/adapter/connection-manager.js +3 -0
- package/dist/map/adapter/connection-manager.js.map +1 -1
- package/dist/map/adapter/event-log.d.ts +87 -0
- package/dist/map/adapter/event-log.d.ts.map +1 -0
- package/dist/map/adapter/event-log.js +122 -0
- package/dist/map/adapter/event-log.js.map +1 -0
- package/dist/map/adapter/event-translator.js +6 -6
- package/dist/map/adapter/event-translator.js.map +1 -1
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
- package/dist/map/adapter/extensions/index.d.ts +10 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +34 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
- package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
- package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
- package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
- package/dist/map/adapter/extensions/rename.d.ts +29 -0
- package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
- package/dist/map/adapter/extensions/rename.js +49 -0
- package/dist/map/adapter/extensions/rename.js.map +1 -0
- package/dist/map/adapter/extensions/task.d.ts.map +1 -1
- package/dist/map/adapter/extensions/task.js +10 -0
- package/dist/map/adapter/extensions/task.js.map +1 -1
- package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
- package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
- package/dist/map/adapter/extensions/update-metadata.js +67 -0
- package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
- package/dist/map/adapter/index.d.ts +2 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +8 -2
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/interface.d.ts +2 -0
- package/dist/map/adapter/interface.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.d.ts +4 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +302 -30
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
- package/dist/map/adapter/subscription-manager.js +5 -1
- package/dist/map/adapter/subscription-manager.js.map +1 -1
- package/dist/map/adapter/types.d.ts +2 -0
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/map-client.d.ts +39 -0
- package/dist/mcp/map-client.d.ts.map +1 -0
- package/dist/mcp/map-client.js +129 -0
- package/dist/mcp/map-client.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +14 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +113 -85
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/types.d.ts +9 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/metrics/metrics.js +1 -1
- package/dist/metrics/metrics.js.map +1 -1
- package/dist/roles/capabilities.d.ts +3 -1
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +17 -7
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +6 -6
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +6 -6
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/registry.d.ts +2 -2
- package/dist/roles/registry.js +2 -2
- package/dist/server/combined-server.d.ts +20 -0
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +107 -8
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts +7 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +91 -8
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +23 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/store/types/events.d.ts +1 -1
- package/dist/store/types/events.d.ts.map +1 -1
- package/dist/task/backend/index.d.ts +47 -29
- package/dist/task/backend/index.d.ts.map +1 -1
- package/dist/task/backend/index.js +109 -71
- package/dist/task/backend/index.js.map +1 -1
- package/dist/task/backend/memory.d.ts +1 -0
- package/dist/task/backend/memory.d.ts.map +1 -1
- package/dist/task/backend/memory.js +3 -0
- package/dist/task/backend/memory.js.map +1 -1
- package/dist/task/backend/opentasks/backend.d.ts +140 -0
- package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
- package/dist/task/backend/opentasks/backend.js +1023 -0
- package/dist/task/backend/opentasks/backend.js.map +1 -0
- package/dist/task/backend/opentasks/client.d.ts +337 -0
- package/dist/task/backend/opentasks/client.d.ts.map +1 -0
- package/dist/task/backend/opentasks/client.js +225 -0
- package/dist/task/backend/opentasks/client.js.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.js +195 -0
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
- package/dist/task/backend/opentasks/index.d.ts +21 -0
- package/dist/task/backend/opentasks/index.d.ts.map +1 -0
- package/dist/task/backend/opentasks/index.js +21 -0
- package/dist/task/backend/opentasks/index.js.map +1 -0
- package/dist/task/backend/opentasks/mapping.d.ts +48 -0
- package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
- package/dist/task/backend/opentasks/mapping.js +77 -0
- package/dist/task/backend/opentasks/mapping.js.map +1 -0
- package/dist/task/backend/types.d.ts +33 -53
- package/dist/task/backend/types.d.ts.map +1 -1
- package/dist/task/backend/types.js +7 -11
- package/dist/task/backend/types.js.map +1 -1
- package/dist/task/backend/unified-tool-provider.d.ts +57 -0
- package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
- package/dist/task/backend/unified-tool-provider.js +623 -0
- package/dist/task/backend/unified-tool-provider.js.map +1 -0
- package/dist/teams/team-loader.d.ts +2 -2
- package/dist/teams/team-loader.js +3 -3
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +2 -0
- package/dist/teams/team-runtime.js.map +1 -1
- package/docs/architecture.md +7 -6
- package/docs/configuration.md +26 -62
- package/docs/implementation-details.md +5 -5
- package/docs/implementation-summary.md +17 -17
- package/docs/plan-self-driving-support.md +4 -4
- package/docs/spec-self-driving-support.md +10 -10
- package/docs/team-templates.md +2 -2
- package/docs/teams.md +3 -3
- package/docs/troubleshooting.md +10 -11
- package/package.json +6 -4
- package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
- package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
- package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
- package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
- package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
- package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
- package/src/acp/__tests__/history.test.ts +8 -4
- package/src/acp/__tests__/integration.test.ts +56 -31
- package/src/acp/__tests__/macro-agent.test.ts +16 -7
- package/src/acp/macro-agent.ts +230 -62
- package/src/acp/types.ts +46 -1
- package/src/agent/__tests__/agent-manager.test.ts +228 -2
- package/src/agent/agent-manager.ts +714 -261
- package/src/agent/types.ts +3 -1
- package/src/api/server.ts +41 -7
- package/src/auth/__tests__/token.test.ts +100 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/token.ts +82 -0
- package/src/cli/__tests__/acp.test.ts +1 -1
- package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
- package/src/cli/acp.ts +130 -72
- package/src/cli/index.ts +120 -14
- package/src/cli/mcp.ts +311 -207
- package/src/cli/parse-args.ts +54 -0
- package/src/cli/stable-instance-id.ts +14 -0
- package/src/config/project-config.ts +190 -27
- package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
- package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +820 -0
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
- package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
- package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
- package/src/map/adapter/__tests__/event-log.test.ts +527 -0
- package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
- package/src/map/adapter/__tests__/extensions.test.ts +408 -0
- package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
- package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
- package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
- package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
- package/src/map/adapter/acp-over-map.ts +777 -92
- package/src/map/adapter/connection-manager.ts +3 -0
- package/src/map/adapter/event-log.ts +208 -0
- package/src/map/adapter/event-translator.ts +6 -6
- package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
- package/src/map/adapter/extensions/index.ts +60 -0
- package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
- package/src/map/adapter/extensions/task.ts +11 -0
- package/src/map/adapter/extensions/update-metadata.ts +126 -0
- package/src/map/adapter/index.ts +28 -0
- package/src/map/adapter/interface.ts +2 -0
- package/src/map/adapter/map-adapter.ts +373 -38
- package/src/map/adapter/subscription-manager.ts +5 -1
- package/src/map/adapter/types.ts +2 -0
- package/src/mcp/__tests__/map-client.test.ts +386 -0
- package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
- package/src/mcp/__tests__/mcp-server.test.ts +100 -1
- package/src/mcp/map-client.ts +177 -0
- package/src/mcp/mcp-server.ts +191 -100
- package/src/mcp/types.ts +6 -1
- package/src/metrics/metrics.ts +1 -1
- package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
- package/src/roles/__tests__/config-loader.test.ts +7 -7
- package/src/roles/capabilities.ts +17 -7
- package/src/roles/config-loader.ts +6 -6
- package/src/roles/registry.ts +2 -2
- package/src/server/__tests__/combined-server.test.ts +94 -21
- package/src/server/combined-server.ts +189 -33
- package/src/steering/__tests__/steering-integration.test.ts +1 -1
- package/src/store/__tests__/event-store.test.ts +236 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +109 -8
- package/src/store/types/agents.ts +16 -0
- package/src/store/types/events.ts +1 -1
- package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
- package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
- package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
- package/src/task/backend/index.ts +156 -106
- package/src/task/backend/memory.ts +4 -0
- package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
- package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
- package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
- package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
- package/src/task/backend/opentasks/backend.ts +1323 -0
- package/src/task/backend/opentasks/client.ts +652 -0
- package/src/task/backend/opentasks/daemon-manager.ts +253 -0
- package/src/task/backend/opentasks/index.ts +69 -0
- package/src/task/backend/opentasks/mapping.ts +94 -0
- package/src/task/backend/types.ts +42 -66
- package/src/task/backend/unified-tool-provider.ts +779 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
- package/src/teams/team-loader.ts +3 -3
- package/src/teams/team-runtime.ts +2 -0
- package/test_fixtures/README.md +2 -3
- package/test_fixtures/fixtures/index.ts +0 -3
- package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
- package/test_fixtures/fixtures/repos/index.ts +1 -3
- package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
- package/test_fixtures/fixtures/repos/types.ts +0 -11
- package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
- package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
- package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
- package/vitest.config.ts +1 -1
- package/vitest.e2e.config.ts +1 -1
- package/vitest.setup.ts +1 -30
- package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
- package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
- package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
- package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
- package/.macro-agent/teams/self-driving/team.yaml +0 -103
- package/.macro-agent/teams/structured/prompts/developer.md +0 -26
- package/.macro-agent/teams/structured/prompts/lead.md +0 -25
- package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
- package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
- package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
- package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
- package/.macro-agent/teams/structured/team.yaml +0 -89
- package/docs/sudocode-integration.md +0 -383
- package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
- package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
- package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
- package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
- package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
- package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
- package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
- package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
- package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
- package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
- package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
- package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
- package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
- package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
- package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
- package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
- package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
- package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
- package/src/task/backend/sudocode/backend.ts +0 -1237
- package/src/task/backend/sudocode/client.ts +0 -515
- package/src/task/backend/sudocode/index.ts +0 -120
- package/src/task/backend/sudocode/mapping.ts +0 -93
- package/src/task/backend/sudocode/server-client.ts +0 -522
- package/src/task/backend/sudocode/standalone-client.ts +0 -623
- package/src/task/backend/sudocode/sync-policy.ts +0 -387
- package/src/task/backend/sudocode/tools.ts +0 -896
- package/src/task/backend/tool-provider.ts +0 -506
- package/test_fixtures/fixtures/sudocode/index.ts +0 -29
- package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
- package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests for UnifiedTaskToolProvider with real OpenTasks daemon
|
|
3
|
+
*
|
|
4
|
+
* Starts a real opentasks daemon, connects via IPCOpenTasksClient,
|
|
5
|
+
* creates an OpenTasksTaskBackend, and exercises all 7 tools end-to-end.
|
|
6
|
+
*
|
|
7
|
+
* Requires: opentasks@0.0.3+ installed
|
|
8
|
+
*
|
|
9
|
+
* @module task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import * as os from "os";
|
|
16
|
+
import { createEventStore, type EventStore } from "../../../../store/event-store.js";
|
|
17
|
+
import { OpenTasksTaskBackend } from "../../opentasks/backend.js";
|
|
18
|
+
import { IPCOpenTasksClient } from "../../opentasks/client.js";
|
|
19
|
+
import {
|
|
20
|
+
UnifiedTaskToolProvider,
|
|
21
|
+
type GetToolContext,
|
|
22
|
+
} from "../../unified-tool-provider.js";
|
|
23
|
+
import type { MCPToolDefinition } from "../../types.js";
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Helpers
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
const TEST_AGENT_ID = "agent_e2e_test";
|
|
30
|
+
const getContext: GetToolContext = () => ({ agent_id: TEST_AGENT_ID });
|
|
31
|
+
|
|
32
|
+
function findTool(
|
|
33
|
+
tools: MCPToolDefinition[],
|
|
34
|
+
name: string
|
|
35
|
+
): MCPToolDefinition {
|
|
36
|
+
const tool = tools.find((t) => t.name === name);
|
|
37
|
+
if (!tool) throw new Error(`Tool not found: ${name}`);
|
|
38
|
+
return tool;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Wait for a condition with timeout
|
|
43
|
+
*/
|
|
44
|
+
async function waitFor(
|
|
45
|
+
condition: () => Promise<boolean> | boolean,
|
|
46
|
+
timeoutMs = 5000,
|
|
47
|
+
intervalMs = 100
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
const start = Date.now();
|
|
50
|
+
while (Date.now() - start < timeoutMs) {
|
|
51
|
+
if (await condition()) return;
|
|
52
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Timed out after ${timeoutMs}ms`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Test Suite
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
describe("UnifiedTaskToolProvider E2E with real OpenTasks", () => {
|
|
62
|
+
let tempDir: string;
|
|
63
|
+
let locationPath: string;
|
|
64
|
+
let daemon: any;
|
|
65
|
+
let socketPath: string;
|
|
66
|
+
let eventStore: EventStore;
|
|
67
|
+
let otClient: IPCOpenTasksClient;
|
|
68
|
+
let backend: OpenTasksTaskBackend;
|
|
69
|
+
let provider: UnifiedTaskToolProvider;
|
|
70
|
+
|
|
71
|
+
beforeAll(async () => {
|
|
72
|
+
// Create temp directory for opentasks data
|
|
73
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "macro-e2e-opentasks-"));
|
|
74
|
+
locationPath = path.join(tempDir, ".opentasks");
|
|
75
|
+
fs.mkdirSync(locationPath, { recursive: true });
|
|
76
|
+
|
|
77
|
+
const registryPath = path.join(tempDir, "registry.json");
|
|
78
|
+
|
|
79
|
+
// Start a real opentasks daemon
|
|
80
|
+
const opentasks = await import("opentasks");
|
|
81
|
+
|
|
82
|
+
daemon = await opentasks.createDaemonWithStore({
|
|
83
|
+
locationPath,
|
|
84
|
+
version: "0.0.3",
|
|
85
|
+
registryPath,
|
|
86
|
+
shutdownTimeoutMs: 2000,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await daemon.start();
|
|
90
|
+
socketPath = daemon.socketPath;
|
|
91
|
+
|
|
92
|
+
// Create macro-agent event store (in-memory)
|
|
93
|
+
eventStore = await createEventStore({ inMemory: true });
|
|
94
|
+
|
|
95
|
+
// Create IPCOpenTasksClient pointing at the real daemon
|
|
96
|
+
otClient = new IPCOpenTasksClient({
|
|
97
|
+
socketPath,
|
|
98
|
+
autoConnect: true,
|
|
99
|
+
timeout: 10000,
|
|
100
|
+
});
|
|
101
|
+
await otClient.connect();
|
|
102
|
+
|
|
103
|
+
// Create OpenTasks backend
|
|
104
|
+
backend = new OpenTasksTaskBackend(eventStore, otClient, {
|
|
105
|
+
socketPath,
|
|
106
|
+
syncStatus: true,
|
|
107
|
+
sourceLabel: "e2e-test",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Create unified tool provider with all 7 tools
|
|
111
|
+
provider = new UnifiedTaskToolProvider(backend, getContext, otClient);
|
|
112
|
+
}, 30000);
|
|
113
|
+
|
|
114
|
+
afterAll(async () => {
|
|
115
|
+
// Teardown in reverse order
|
|
116
|
+
try {
|
|
117
|
+
otClient?.disconnect();
|
|
118
|
+
} catch { /* ignore */ }
|
|
119
|
+
try {
|
|
120
|
+
await daemon?.stop();
|
|
121
|
+
} catch { /* ignore */ }
|
|
122
|
+
try {
|
|
123
|
+
await eventStore?.close();
|
|
124
|
+
} catch { /* ignore */ }
|
|
125
|
+
try {
|
|
126
|
+
if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true });
|
|
127
|
+
} catch { /* ignore */ }
|
|
128
|
+
}, 15000);
|
|
129
|
+
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
131
|
+
// Tool Exposure
|
|
132
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
it("should expose all 8 tools with OpenTasks client", () => {
|
|
135
|
+
const tools = provider.getTools();
|
|
136
|
+
expect(tools).toHaveLength(8);
|
|
137
|
+
expect(tools.map((t) => t.name)).toEqual([
|
|
138
|
+
"create_task",
|
|
139
|
+
"get_task",
|
|
140
|
+
"list_tasks",
|
|
141
|
+
"assign_task",
|
|
142
|
+
"task",
|
|
143
|
+
"link",
|
|
144
|
+
"annotate",
|
|
145
|
+
"list_providers",
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
+
// Core CRUD Tools (real backend → real daemon)
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
describe("create_task → get_task roundtrip", () => {
|
|
154
|
+
let createdTaskId: string;
|
|
155
|
+
|
|
156
|
+
it("should create a task via create_task tool", async () => {
|
|
157
|
+
const tool = findTool(provider.getTools(), "create_task");
|
|
158
|
+
const result = (await tool.handler({
|
|
159
|
+
description: "E2E test task",
|
|
160
|
+
})) as { task_id: string; status: string; external_id?: string };
|
|
161
|
+
|
|
162
|
+
expect(result.task_id).toMatch(/^task_/);
|
|
163
|
+
expect(result.status).toBe("pending");
|
|
164
|
+
createdTaskId = result.task_id;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should retrieve the task via get_task tool", async () => {
|
|
168
|
+
const tool = findTool(provider.getTools(), "get_task");
|
|
169
|
+
const result = (await tool.handler({
|
|
170
|
+
task_id: createdTaskId,
|
|
171
|
+
})) as { id: string; description: string; status: string };
|
|
172
|
+
|
|
173
|
+
expect(result.id).toBe(createdTaskId);
|
|
174
|
+
expect(result.description).toBe("E2E test task");
|
|
175
|
+
expect(result.status).toBe("pending");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("list_tasks", () => {
|
|
180
|
+
it("should list tasks with filters", async () => {
|
|
181
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
182
|
+
const listTool = findTool(provider.getTools(), "list_tasks");
|
|
183
|
+
|
|
184
|
+
// Create a couple tasks
|
|
185
|
+
await createTool.handler({ description: "List test A" });
|
|
186
|
+
await createTool.handler({ description: "List test B" });
|
|
187
|
+
|
|
188
|
+
const result = (await listTool.handler({})) as {
|
|
189
|
+
tasks: Array<{ id: string; description: string }>;
|
|
190
|
+
total: number;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
expect(result.total).toBeGreaterThanOrEqual(2);
|
|
194
|
+
expect(result.tasks.some((t) => t.description === "List test A")).toBe(true);
|
|
195
|
+
expect(result.tasks.some((t) => t.description === "List test B")).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("assign_task", () => {
|
|
200
|
+
it("should assign a task to an agent", async () => {
|
|
201
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
202
|
+
const assignTool = findTool(provider.getTools(), "assign_task");
|
|
203
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
204
|
+
|
|
205
|
+
// Create
|
|
206
|
+
const created = (await createTool.handler({
|
|
207
|
+
description: "Assign test task",
|
|
208
|
+
})) as { task_id: string };
|
|
209
|
+
|
|
210
|
+
// Assign
|
|
211
|
+
const assignResult = (await assignTool.handler({
|
|
212
|
+
task_id: created.task_id,
|
|
213
|
+
agent_id: "agent_worker_1",
|
|
214
|
+
})) as { assigned_agent: string; assigned: boolean };
|
|
215
|
+
|
|
216
|
+
expect(assignResult.assigned_agent).toBe("agent_worker_1");
|
|
217
|
+
expect(assignResult.assigned).toBe(true);
|
|
218
|
+
|
|
219
|
+
// Verify
|
|
220
|
+
const task = (await getTool.handler({
|
|
221
|
+
task_id: created.task_id,
|
|
222
|
+
})) as { assigned_agent: string; status: string };
|
|
223
|
+
|
|
224
|
+
expect(task.assigned_agent).toBe("agent_worker_1");
|
|
225
|
+
expect(task.status).toBe("assigned");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
// OpenTasks Graph Tools (real daemon)
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
describe("task tool", () => {
|
|
234
|
+
it("should transition a task through its lifecycle and sync to EventStore", async () => {
|
|
235
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
236
|
+
const taskTool = findTool(provider.getTools(), "task");
|
|
237
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
238
|
+
|
|
239
|
+
const created = (await createTool.handler({
|
|
240
|
+
description: "Lifecycle test task",
|
|
241
|
+
})) as { task_id: string; external_id?: string };
|
|
242
|
+
|
|
243
|
+
const taskDetails = (await getTool.handler({
|
|
244
|
+
task_id: created.task_id,
|
|
245
|
+
})) as { external_id?: string; status: string };
|
|
246
|
+
|
|
247
|
+
const externalId = taskDetails.external_id;
|
|
248
|
+
expect(externalId).toBeDefined();
|
|
249
|
+
expect(taskDetails.status).toBe("pending");
|
|
250
|
+
|
|
251
|
+
// Start the task via opentasks daemon
|
|
252
|
+
const startResult = await taskTool.handler({
|
|
253
|
+
transition: { id: externalId, action: "start" },
|
|
254
|
+
});
|
|
255
|
+
expect(startResult).toBeDefined();
|
|
256
|
+
|
|
257
|
+
// Verify EventStore was synced — get_task reads from EventStore
|
|
258
|
+
const afterStart = (await getTool.handler({
|
|
259
|
+
task_id: created.task_id,
|
|
260
|
+
})) as { status: string };
|
|
261
|
+
expect(afterStart.status).toBe("in_progress");
|
|
262
|
+
|
|
263
|
+
// Complete the task via opentasks daemon
|
|
264
|
+
const completeResult = await taskTool.handler({
|
|
265
|
+
transition: { id: externalId, action: "complete" },
|
|
266
|
+
});
|
|
267
|
+
expect(completeResult).toBeDefined();
|
|
268
|
+
|
|
269
|
+
// Verify EventStore was synced
|
|
270
|
+
const afterComplete = (await getTool.handler({
|
|
271
|
+
task_id: created.task_id,
|
|
272
|
+
})) as { status: string };
|
|
273
|
+
expect(afterComplete.status).toBe("completed");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should sync task assignment via task tool to EventStore", async () => {
|
|
277
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
278
|
+
const taskTool = findTool(provider.getTools(), "task");
|
|
279
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
280
|
+
|
|
281
|
+
const created = (await createTool.handler({
|
|
282
|
+
description: "Assign sync test task",
|
|
283
|
+
})) as { task_id: string };
|
|
284
|
+
|
|
285
|
+
const taskDetails = (await getTool.handler({
|
|
286
|
+
task_id: created.task_id,
|
|
287
|
+
})) as { external_id: string };
|
|
288
|
+
|
|
289
|
+
// Assign via task tool (opentasks daemon path)
|
|
290
|
+
await taskTool.handler({
|
|
291
|
+
assign: { id: taskDetails.external_id, assignee: "agent_worker_2" },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Verify EventStore was synced
|
|
295
|
+
const afterAssign = (await getTool.handler({
|
|
296
|
+
task_id: created.task_id,
|
|
297
|
+
})) as { assigned_agent: string };
|
|
298
|
+
expect(afterAssign.assigned_agent).toBe("agent_worker_2");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("should query ready tasks", async () => {
|
|
302
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
303
|
+
const taskTool = findTool(provider.getTools(), "task");
|
|
304
|
+
|
|
305
|
+
// Create a fresh task (should be ready since no blockers)
|
|
306
|
+
await createTool.handler({ description: "Ready test task" });
|
|
307
|
+
|
|
308
|
+
const readyResult = (await taskTool.handler({
|
|
309
|
+
ready: {},
|
|
310
|
+
})) as { type?: string; items?: unknown[] };
|
|
311
|
+
|
|
312
|
+
expect(readyResult).toBeDefined();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should get valid actions for a task", async () => {
|
|
316
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
317
|
+
const taskTool = findTool(provider.getTools(), "task");
|
|
318
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
319
|
+
|
|
320
|
+
const created = (await createTool.handler({
|
|
321
|
+
description: "Valid actions test",
|
|
322
|
+
})) as { task_id: string };
|
|
323
|
+
|
|
324
|
+
const taskDetails = (await getTool.handler({
|
|
325
|
+
task_id: created.task_id,
|
|
326
|
+
})) as { external_id?: string };
|
|
327
|
+
|
|
328
|
+
const result = await taskTool.handler({
|
|
329
|
+
validActions: { id: taskDetails.external_id! },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(result).toBeDefined();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("link tool", () => {
|
|
337
|
+
it("should create and remove edges between tasks", async () => {
|
|
338
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
339
|
+
const linkTool = findTool(provider.getTools(), "link");
|
|
340
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
341
|
+
|
|
342
|
+
// Create two tasks
|
|
343
|
+
const blocker = (await createTool.handler({
|
|
344
|
+
description: "Blocker task",
|
|
345
|
+
})) as { task_id: string };
|
|
346
|
+
const blocked = (await createTool.handler({
|
|
347
|
+
description: "Blocked task",
|
|
348
|
+
})) as { task_id: string };
|
|
349
|
+
|
|
350
|
+
// Get external IDs
|
|
351
|
+
const blockerDetails = (await getTool.handler({
|
|
352
|
+
task_id: blocker.task_id,
|
|
353
|
+
})) as { external_id: string };
|
|
354
|
+
const blockedDetails = (await getTool.handler({
|
|
355
|
+
task_id: blocked.task_id,
|
|
356
|
+
})) as { external_id: string };
|
|
357
|
+
|
|
358
|
+
// Create a "blocks" edge
|
|
359
|
+
const createResult = (await linkTool.handler({
|
|
360
|
+
from_id: blockerDetails.external_id,
|
|
361
|
+
to_id: blockedDetails.external_id,
|
|
362
|
+
type: "blocks",
|
|
363
|
+
})) as { created: boolean };
|
|
364
|
+
|
|
365
|
+
expect(createResult.created).toBe(true);
|
|
366
|
+
|
|
367
|
+
// Remove the edge
|
|
368
|
+
const removeResult = (await linkTool.handler({
|
|
369
|
+
from_id: blockerDetails.external_id,
|
|
370
|
+
to_id: blockedDetails.external_id,
|
|
371
|
+
type: "blocks",
|
|
372
|
+
remove: true,
|
|
373
|
+
})) as { removed: boolean };
|
|
374
|
+
|
|
375
|
+
expect(removeResult.removed).toBe(true);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("annotate tool", () => {
|
|
380
|
+
it("should create feedback on a task", async () => {
|
|
381
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
382
|
+
const annotateTool = findTool(provider.getTools(), "annotate");
|
|
383
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
384
|
+
|
|
385
|
+
// Create a task
|
|
386
|
+
const created = (await createTool.handler({
|
|
387
|
+
description: "Feedback target task",
|
|
388
|
+
})) as { task_id: string };
|
|
389
|
+
|
|
390
|
+
const details = (await getTool.handler({
|
|
391
|
+
task_id: created.task_id,
|
|
392
|
+
})) as { external_id: string };
|
|
393
|
+
|
|
394
|
+
// Add a comment
|
|
395
|
+
const annotateResult = (await annotateTool.handler({
|
|
396
|
+
target_id: details.external_id,
|
|
397
|
+
content: "E2E test feedback comment",
|
|
398
|
+
feedback_type: "comment",
|
|
399
|
+
})) as { feedback_id: string; created: boolean; type: string };
|
|
400
|
+
|
|
401
|
+
expect(annotateResult.created).toBe(true);
|
|
402
|
+
expect(annotateResult.type).toBe("comment");
|
|
403
|
+
expect(annotateResult.feedback_id).toBeDefined();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should resolve feedback", async () => {
|
|
407
|
+
const createTool = findTool(provider.getTools(), "create_task");
|
|
408
|
+
const annotateTool = findTool(provider.getTools(), "annotate");
|
|
409
|
+
const getTool = findTool(provider.getTools(), "get_task");
|
|
410
|
+
|
|
411
|
+
const created = (await createTool.handler({
|
|
412
|
+
description: "Resolve feedback target",
|
|
413
|
+
})) as { task_id: string };
|
|
414
|
+
|
|
415
|
+
const details = (await getTool.handler({
|
|
416
|
+
task_id: created.task_id,
|
|
417
|
+
})) as { external_id: string };
|
|
418
|
+
|
|
419
|
+
// Create feedback
|
|
420
|
+
const feedback = (await annotateTool.handler({
|
|
421
|
+
target_id: details.external_id,
|
|
422
|
+
content: "Feedback to resolve",
|
|
423
|
+
feedback_type: "suggestion",
|
|
424
|
+
})) as { feedback_id: string };
|
|
425
|
+
|
|
426
|
+
// Resolve it
|
|
427
|
+
const resolveResult = (await annotateTool.handler({
|
|
428
|
+
target_id: details.external_id,
|
|
429
|
+
resolve: feedback.feedback_id,
|
|
430
|
+
})) as { resolved: boolean; feedback_id: string };
|
|
431
|
+
|
|
432
|
+
expect(resolveResult.resolved).toBe(true);
|
|
433
|
+
expect(resolveResult.feedback_id).toBe(feedback.feedback_id);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
438
|
+
// Full Workflow
|
|
439
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
describe("full workflow", () => {
|
|
442
|
+
it("should execute a complete task lifecycle: create → assign → link → annotate → transition", async () => {
|
|
443
|
+
const tools = provider.getTools();
|
|
444
|
+
const createTool = findTool(tools, "create_task");
|
|
445
|
+
const getTool = findTool(tools, "get_task");
|
|
446
|
+
const assignTool = findTool(tools, "assign_task");
|
|
447
|
+
const linkTool = findTool(tools, "link");
|
|
448
|
+
const annotateTool = findTool(tools, "annotate");
|
|
449
|
+
const taskTool = findTool(tools, "task");
|
|
450
|
+
|
|
451
|
+
// 1. Create parent and child tasks
|
|
452
|
+
const parent = (await createTool.handler({
|
|
453
|
+
description: "Full workflow parent task",
|
|
454
|
+
})) as { task_id: string };
|
|
455
|
+
|
|
456
|
+
const child = (await createTool.handler({
|
|
457
|
+
description: "Full workflow child task",
|
|
458
|
+
parent_task: parent.task_id,
|
|
459
|
+
})) as { task_id: string };
|
|
460
|
+
|
|
461
|
+
// 2. Get external IDs
|
|
462
|
+
const parentDetails = (await getTool.handler({
|
|
463
|
+
task_id: parent.task_id,
|
|
464
|
+
})) as { external_id: string };
|
|
465
|
+
|
|
466
|
+
const childDetails = (await getTool.handler({
|
|
467
|
+
task_id: child.task_id,
|
|
468
|
+
})) as { external_id: string };
|
|
469
|
+
|
|
470
|
+
expect(parentDetails.external_id).toBeDefined();
|
|
471
|
+
expect(childDetails.external_id).toBeDefined();
|
|
472
|
+
|
|
473
|
+
// 3. Create a "blocks" link: parent blocks child
|
|
474
|
+
const linkResult = (await linkTool.handler({
|
|
475
|
+
from_id: parentDetails.external_id,
|
|
476
|
+
to_id: childDetails.external_id,
|
|
477
|
+
type: "blocks",
|
|
478
|
+
})) as { created: boolean };
|
|
479
|
+
|
|
480
|
+
expect(linkResult.created).toBe(true);
|
|
481
|
+
|
|
482
|
+
// 4. Assign parent task
|
|
483
|
+
const assignResult = (await assignTool.handler({
|
|
484
|
+
task_id: parent.task_id,
|
|
485
|
+
agent_id: TEST_AGENT_ID,
|
|
486
|
+
})) as { assigned: boolean };
|
|
487
|
+
|
|
488
|
+
expect(assignResult.assigned).toBe(true);
|
|
489
|
+
|
|
490
|
+
// 5. Add feedback on the parent
|
|
491
|
+
const feedbackResult = (await annotateTool.handler({
|
|
492
|
+
target_id: parentDetails.external_id,
|
|
493
|
+
content: "Starting work on this task",
|
|
494
|
+
feedback_type: "comment",
|
|
495
|
+
})) as { created: boolean; feedback_id: string };
|
|
496
|
+
|
|
497
|
+
expect(feedbackResult.created).toBe(true);
|
|
498
|
+
|
|
499
|
+
// 6. Start parent via task tool
|
|
500
|
+
const startResult = (await taskTool.handler({
|
|
501
|
+
transition: { id: parentDetails.external_id, action: "start" },
|
|
502
|
+
})) as { success: boolean };
|
|
503
|
+
|
|
504
|
+
expect(startResult).toBeDefined();
|
|
505
|
+
|
|
506
|
+
// 7. Complete parent via task tool
|
|
507
|
+
const completeResult = (await taskTool.handler({
|
|
508
|
+
transition: { id: parentDetails.external_id, action: "complete" },
|
|
509
|
+
})) as { success: boolean };
|
|
510
|
+
|
|
511
|
+
expect(completeResult).toBeDefined();
|
|
512
|
+
|
|
513
|
+
// 8. Verify parent is completed in EventStore (syncExternalTransition)
|
|
514
|
+
const finalParent = (await getTool.handler({
|
|
515
|
+
task_id: parent.task_id,
|
|
516
|
+
})) as { status: string; external_id: string; assigned_agent: string };
|
|
517
|
+
|
|
518
|
+
expect(finalParent).toBeDefined();
|
|
519
|
+
expect(finalParent.external_id).toBe(parentDetails.external_id);
|
|
520
|
+
expect(finalParent.status).toBe("completed");
|
|
521
|
+
expect(finalParent.assigned_agent).toBe(TEST_AGENT_ID);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
});
|