@moltzap/client 2026.529.0 → 2026.718.0
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/agent-client.d.ts +2 -65
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +1 -286
- package/dist/agent-client.js.map +1 -1
- package/dist/app-client.d.ts +3 -0
- package/dist/app-client.d.ts.map +1 -0
- package/dist/app-client.js +2 -0
- package/dist/app-client.js.map +1 -0
- package/dist/auth.d.ts +6 -15
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +12 -16
- package/dist/auth.js.map +1 -1
- package/dist/channel-base/format-cross-conv.d.ts +10 -15
- package/dist/channel-base/format-cross-conv.d.ts.map +1 -1
- package/dist/channel-base/format-cross-conv.js +10 -15
- package/dist/channel-base/format-cross-conv.js.map +1 -1
- package/dist/channel-base/format-group-block.d.ts +8 -10
- package/dist/channel-base/format-group-block.d.ts.map +1 -1
- package/dist/channel-base/format-group-block.js +8 -10
- package/dist/channel-base/format-group-block.js.map +1 -1
- package/dist/channel-base/index.d.ts +3 -62
- package/dist/channel-base/index.d.ts.map +1 -1
- package/dist/channel-base/index.js +2 -65
- package/dist/channel-base/index.js.map +1 -1
- package/dist/channel-base/lease-guard.d.ts +0 -2
- package/dist/channel-base/lease-guard.d.ts.map +1 -1
- package/dist/channel-base/lease-guard.js +0 -2
- package/dist/channel-base/lease-guard.js.map +1 -1
- package/dist/channel-base/lease-store.d.ts +2 -3
- package/dist/channel-base/lease-store.d.ts.map +1 -1
- package/dist/channel-base/lease-store.js +2 -3
- package/dist/channel-base/lease-store.js.map +1 -1
- package/dist/channel-base/lease.d.ts +25 -31
- package/dist/channel-base/lease.d.ts.map +1 -1
- package/dist/channel-base/lease.js +29 -45
- package/dist/channel-base/lease.js.map +1 -1
- package/dist/channel-core-enrichment.d.ts +1 -1
- package/dist/channel-core-enrichment.d.ts.map +1 -1
- package/dist/channel-core-test-support.d.ts +17 -11
- package/dist/channel-core-test-support.d.ts.map +1 -1
- package/dist/channel-core-test-support.js +5 -6
- package/dist/channel-core-test-support.js.map +1 -1
- package/dist/channel-core.d.ts +24 -29
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +18 -42
- package/dist/channel-core.js.map +1 -1
- package/dist/cli/commands/agents.d.ts +3 -6
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +14 -50
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/contacts.d.ts +5 -6
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +15 -43
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts +8 -18
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +14 -65
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +13 -35
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +21 -46
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/register.d.ts +10 -12
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +47 -133
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/send.d.ts +23 -36
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +25 -72
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/start.d.ts +10 -124
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +28 -510
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +8 -9
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/test-transport.d.ts +12 -5
- package/dist/cli/commands/test-transport.d.ts.map +1 -1
- package/dist/cli/commands/test-transport.js +15 -15
- package/dist/cli/commands/test-transport.js.map +1 -1
- package/dist/cli/index.js +13 -40
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +9 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/socket-client.d.ts +7 -14
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +15 -38
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +53 -116
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +45 -295
- package/dist/cli/transport.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test-utils.d.ts +13 -0
- package/dist/config.test-utils.d.ts.map +1 -0
- package/dist/config.test-utils.js +46 -0
- package/dist/config.test-utils.js.map +1 -0
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -12
- package/dist/index.js.map +1 -1
- package/dist/local-daemon-rpc.d.ts +259 -8
- package/dist/local-daemon-rpc.d.ts.map +1 -1
- package/dist/local-daemon-rpc.js +189 -12
- package/dist/local-daemon-rpc.js.map +1 -1
- package/dist/{runtime/local-history.d.ts → local-history.d.ts} +16 -21
- package/dist/local-history.d.ts.map +1 -0
- package/dist/{runtime/local-history.js → local-history.js} +16 -21
- package/dist/local-history.js.map +1 -0
- package/dist/local-paths.d.ts +0 -2
- package/dist/local-paths.d.ts.map +1 -1
- package/dist/local-paths.js +0 -4
- package/dist/local-paths.js.map +1 -1
- package/dist/local-socket-server.d.ts +22 -0
- package/dist/local-socket-server.d.ts.map +1 -0
- package/dist/{runtime/local-socket-server.js → local-socket-server.js} +4 -9
- package/dist/local-socket-server.js.map +1 -0
- package/dist/message-rendering.d.ts +3 -0
- package/dist/message-rendering.d.ts.map +1 -0
- package/dist/message-rendering.js +7 -0
- package/dist/message-rendering.js.map +1 -0
- package/dist/notification/__tests__/{snapshot-semantics.types-check.d.ts → subscribe-signatures.types-check.d.ts} +1 -1
- package/dist/notification/__tests__/subscribe-signatures.types-check.d.ts.map +1 -0
- package/dist/notification/__tests__/subscribe-signatures.types-check.js +55 -0
- package/dist/notification/__tests__/subscribe-signatures.types-check.js.map +1 -0
- package/dist/notification/errors.d.ts +18 -6
- package/dist/notification/errors.d.ts.map +1 -1
- package/dist/notification/errors.js +13 -15
- package/dist/notification/errors.js.map +1 -1
- package/dist/notification/index.d.ts +5 -0
- package/dist/notification/index.d.ts.map +1 -0
- package/dist/notification/index.js +5 -0
- package/dist/notification/index.js.map +1 -0
- package/dist/notification/stream.d.ts +33 -49
- package/dist/notification/stream.d.ts.map +1 -1
- package/dist/notification/stream.js +33 -76
- package/dist/notification/stream.js.map +1 -1
- package/dist/pagination.d.ts +20 -10
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +8 -9
- package/dist/pagination.js.map +1 -1
- package/dist/profile.d.ts +92 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +149 -0
- package/dist/profile.js.map +1 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js.map +1 -0
- package/dist/service-archive-purge.d.ts +2 -1
- package/dist/service-archive-purge.d.ts.map +1 -1
- package/dist/service-archive-purge.js.map +1 -1
- package/dist/{runtime/service-helpers.d.ts → service-helpers.d.ts} +5 -11
- package/dist/service-helpers.d.ts.map +1 -0
- package/dist/{runtime/service-helpers.js → service-helpers.js} +2 -9
- package/dist/service-helpers.js.map +1 -0
- package/dist/service-local-daemon.d.ts +22 -0
- package/dist/service-local-daemon.d.ts.map +1 -0
- package/dist/service-local-daemon.js +185 -0
- package/dist/service-local-daemon.js.map +1 -0
- package/dist/service.d.ts +65 -57
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +205 -156
- package/dist/service.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +3 -2
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +26 -18
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +28 -42
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/ids.d.ts +3 -1
- package/dist/test-utils/ids.d.ts.map +1 -1
- package/dist/test-utils/ids.js.map +1 -1
- package/dist/test-utils/index.d.ts +9 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +8 -1
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/standalone-provisioning.d.ts +17 -0
- package/dist/test-utils/standalone-provisioning.d.ts.map +1 -0
- package/dist/test-utils/standalone-provisioning.js +14 -0
- package/dist/test-utils/standalone-provisioning.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +49 -27
- package/dist/auth.test.d.ts +0 -2
- package/dist/auth.test.d.ts.map +0 -1
- package/dist/auth.test.js +0 -121
- package/dist/auth.test.js.map +0 -1
- package/dist/channel-core-context.test.d.ts +0 -2
- package/dist/channel-core-context.test.d.ts.map +0 -1
- package/dist/channel-core-context.test.js +0 -342
- package/dist/channel-core-context.test.js.map +0 -1
- package/dist/channel-core-dispatch.test.d.ts +0 -2
- package/dist/channel-core-dispatch.test.d.ts.map +0 -1
- package/dist/channel-core-dispatch.test.js +0 -557
- package/dist/channel-core-dispatch.test.js.map +0 -1
- package/dist/channel-core.test.d.ts +0 -2
- package/dist/channel-core.test.d.ts.map +0 -1
- package/dist/channel-core.test.js +0 -232
- package/dist/channel-core.test.js.map +0 -1
- package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts +0 -2
- package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts.map +0 -1
- package/dist/cli/__tests__/cli-multi-agent.int.test.js +0 -132
- package/dist/cli/__tests__/cli-multi-agent.int.test.js.map +0 -1
- package/dist/cli/commands/invite.d.ts +0 -7
- package/dist/cli/commands/invite.d.ts.map +0 -1
- package/dist/cli/commands/invite.js +0 -11
- package/dist/cli/commands/invite.js.map +0 -1
- package/dist/cli/commands/messages.test.d.ts +0 -2
- package/dist/cli/commands/messages.test.d.ts.map +0 -1
- package/dist/cli/commands/messages.test.js +0 -99
- package/dist/cli/commands/messages.test.js.map +0 -1
- package/dist/cli/commands/ping.d.ts +0 -7
- package/dist/cli/commands/ping.d.ts.map +0 -1
- package/dist/cli/commands/ping.js +0 -37
- package/dist/cli/commands/ping.js.map +0 -1
- package/dist/cli/commands/register.test.d.ts +0 -2
- package/dist/cli/commands/register.test.d.ts.map +0 -1
- package/dist/cli/commands/register.test.js +0 -91
- package/dist/cli/commands/register.test.js.map +0 -1
- package/dist/cli/commands/send.test.d.ts +0 -2
- package/dist/cli/commands/send.test.d.ts.map +0 -1
- package/dist/cli/commands/send.test.js +0 -59
- package/dist/cli/commands/send.test.js.map +0 -1
- package/dist/cli/commands/start.test.d.ts +0 -2
- package/dist/cli/commands/start.test.d.ts.map +0 -1
- package/dist/cli/commands/start.test.js +0 -624
- package/dist/cli/commands/start.test.js.map +0 -1
- package/dist/cli/commands/whoami.d.ts +0 -8
- package/dist/cli/commands/whoami.d.ts.map +0 -1
- package/dist/cli/commands/whoami.js +0 -28
- package/dist/cli/commands/whoami.js.map +0 -1
- package/dist/cli/config.d.ts +0 -66
- package/dist/cli/config.d.ts.map +0 -1
- package/dist/cli/config.js +0 -148
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/config.test.d.ts +0 -2
- package/dist/cli/config.test.d.ts.map +0 -1
- package/dist/cli/config.test.js +0 -89
- package/dist/cli/config.test.js.map +0 -1
- package/dist/cli/http-client.d.ts +0 -17
- package/dist/cli/http-client.d.ts.map +0 -1
- package/dist/cli/http-client.js +0 -38
- package/dist/cli/http-client.js.map +0 -1
- package/dist/cli/profile.d.ts +0 -126
- package/dist/cli/profile.d.ts.map +0 -1
- package/dist/cli/profile.js +0 -231
- package/dist/cli/profile.js.map +0 -1
- package/dist/cli/profile.test.d.ts +0 -2
- package/dist/cli/profile.test.d.ts.map +0 -1
- package/dist/cli/profile.test.js +0 -369
- package/dist/cli/profile.test.js.map +0 -1
- package/dist/cli/transport.test.d.ts +0 -2
- package/dist/cli/transport.test.d.ts.map +0 -1
- package/dist/cli/transport.test.js +0 -312
- package/dist/cli/transport.test.js.map +0 -1
- package/dist/notification/__tests__/filter-equivalence.test.d.ts +0 -2
- package/dist/notification/__tests__/filter-equivalence.test.d.ts.map +0 -1
- package/dist/notification/__tests__/filter-equivalence.test.js +0 -123
- package/dist/notification/__tests__/filter-equivalence.test.js.map +0 -1
- package/dist/notification/__tests__/snapshot-semantics.test.d.ts +0 -2
- package/dist/notification/__tests__/snapshot-semantics.test.d.ts.map +0 -1
- package/dist/notification/__tests__/snapshot-semantics.test.js +0 -261
- package/dist/notification/__tests__/snapshot-semantics.test.js.map +0 -1
- package/dist/notification/__tests__/snapshot-semantics.types-check.d.ts.map +0 -1
- package/dist/notification/__tests__/snapshot-semantics.types-check.js +0 -62
- package/dist/notification/__tests__/snapshot-semantics.types-check.js.map +0 -1
- package/dist/pagination.test.d.ts +0 -2
- package/dist/pagination.test.d.ts.map +0 -1
- package/dist/pagination.test.js +0 -72
- package/dist/pagination.test.js.map +0 -1
- package/dist/runtime/close-info.d.ts +0 -84
- package/dist/runtime/close-info.d.ts.map +0 -1
- package/dist/runtime/close-info.js +0 -102
- package/dist/runtime/close-info.js.map +0 -1
- package/dist/runtime/close-info.test.d.ts +0 -2
- package/dist/runtime/close-info.test.d.ts.map +0 -1
- package/dist/runtime/close-info.test.js +0 -109
- package/dist/runtime/close-info.test.js.map +0 -1
- package/dist/runtime/errors.d.ts +0 -11
- package/dist/runtime/errors.d.ts.map +0 -1
- package/dist/runtime/errors.js +0 -41
- package/dist/runtime/errors.js.map +0 -1
- package/dist/runtime/errors.test.d.ts +0 -2
- package/dist/runtime/errors.test.d.ts.map +0 -1
- package/dist/runtime/errors.test.js +0 -79
- package/dist/runtime/errors.test.js.map +0 -1
- package/dist/runtime/frame.d.ts +0 -8
- package/dist/runtime/frame.d.ts.map +0 -1
- package/dist/runtime/frame.js +0 -12
- package/dist/runtime/frame.js.map +0 -1
- package/dist/runtime/frame.test.d.ts +0 -2
- package/dist/runtime/frame.test.d.ts.map +0 -1
- package/dist/runtime/frame.test.js +0 -59
- package/dist/runtime/frame.test.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -10
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -9
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/local-history.d.ts.map +0 -1
- package/dist/runtime/local-history.js.map +0 -1
- package/dist/runtime/local-service-commands.d.ts +0 -26
- package/dist/runtime/local-service-commands.d.ts.map +0 -1
- package/dist/runtime/local-service-commands.js +0 -29
- package/dist/runtime/local-service-commands.js.map +0 -1
- package/dist/runtime/local-socket-server.d.ts +0 -20
- package/dist/runtime/local-socket-server.d.ts.map +0 -1
- package/dist/runtime/local-socket-server.js.map +0 -1
- package/dist/runtime/refs.d.ts.map +0 -1
- package/dist/runtime/refs.js.map +0 -1
- package/dist/runtime/runtime.test.d.ts +0 -2
- package/dist/runtime/runtime.test.d.ts.map +0 -1
- package/dist/runtime/runtime.test.js +0 -37
- package/dist/runtime/runtime.test.js.map +0 -1
- package/dist/runtime/service-helpers.d.ts.map +0 -1
- package/dist/runtime/service-helpers.js.map +0 -1
- package/dist/runtime/service-teardown.d.ts +0 -5
- package/dist/runtime/service-teardown.d.ts.map +0 -1
- package/dist/runtime/service-teardown.js +0 -36
- package/dist/runtime/service-teardown.js.map +0 -1
- package/dist/runtime/service-teardown.test.d.ts +0 -2
- package/dist/runtime/service-teardown.test.d.ts.map +0 -1
- package/dist/runtime/service-teardown.test.js +0 -80
- package/dist/runtime/service-teardown.test.js.map +0 -1
- package/dist/runtime/subscribers.d.ts +0 -152
- package/dist/runtime/subscribers.d.ts.map +0 -1
- package/dist/runtime/subscribers.js +0 -222
- package/dist/runtime/subscribers.js.map +0 -1
- package/dist/service-socket-path.test.d.ts +0 -2
- package/dist/service-socket-path.test.d.ts.map +0 -1
- package/dist/service-socket-path.test.js +0 -87
- package/dist/service-socket-path.test.js.map +0 -1
- package/dist/service.test.d.ts +0 -2
- package/dist/service.test.d.ts.map +0 -1
- package/dist/service.test.js +0 -796
- package/dist/service.test.js.map +0 -1
- package/dist/test/index.d.ts +0 -47
- package/dist/test/index.d.ts.map +0 -1
- package/dist/test/index.js +0 -39
- package/dist/test/index.js.map +0 -1
- package/dist/test-utils/conformance-adapter.d.ts +0 -40
- package/dist/test-utils/conformance-adapter.d.ts.map +0 -1
- package/dist/test-utils/conformance-adapter.js +0 -250
- package/dist/test-utils/conformance-adapter.js.map +0 -1
- package/dist/tm-client-test-support.d.ts +0 -184
- package/dist/tm-client-test-support.d.ts.map +0 -1
- package/dist/tm-client-test-support.js +0 -466
- package/dist/tm-client-test-support.js.map +0 -1
- package/dist/tm-client.d.ts +0 -269
- package/dist/tm-client.d.ts.map +0 -1
- package/dist/tm-client.js +0 -518
- package/dist/tm-client.js.map +0 -1
- package/dist/tm-client.test.d.ts +0 -2
- package/dist/tm-client.test.d.ts.map +0 -1
- package/dist/tm-client.test.js +0 -490
- package/dist/tm-client.test.js.map +0 -1
- /package/dist/{runtime/refs.d.ts → refs.d.ts} +0 -0
- /package/dist/{runtime/refs.js → refs.js} +0 -0
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for `moltzap start` (spec D2 #599 / sub-issue #661).
|
|
3
|
-
*
|
|
4
|
-
* Coverage map matches per-flow doc 09 §7. Each test body is a named
|
|
5
|
-
* helper to keep describe-blocks under the 50-line per-function limit
|
|
6
|
-
* (matches the pattern in `transport.test.ts`).
|
|
7
|
-
*/
|
|
8
|
-
import { Effect } from "effect";
|
|
9
|
-
import * as fc from "fast-check";
|
|
10
|
-
import { it as effectIt } from "@effect/vitest";
|
|
11
|
-
import { afterEach, beforeEach, describe, expect, it as plainIt, vi, } from "vitest";
|
|
12
|
-
import { AgentsLookupByName, DEFAULT_APP_ID, MessagesSend, TaskConversationList, TaskRequest, } from "@moltzap/protocol";
|
|
13
|
-
import { Transport } from "../transport.js";
|
|
14
|
-
import { makeFakeTransport } from "./test-transport.js";
|
|
15
|
-
import { runStartHandler, startCommand } from "./start.js";
|
|
16
|
-
const it = effectIt.effect;
|
|
17
|
-
// ─── Contractual literals (also asserted in `help` test) ──────────────────
|
|
18
|
-
const HELP_SYNOPSIS = "Start a task with named participants";
|
|
19
|
-
const HELP_MENTIONS_MESSAGES_SEND = "MessagesSend";
|
|
20
|
-
const HELP_APP_ID_FLAG = "--app-id";
|
|
21
|
-
const HELP_EXIT_CODES_HEADING = "Exit codes:";
|
|
22
|
-
// ─── Fixtures ─────────────────────────────────────────────────────────────
|
|
23
|
-
const CONVERSATION_NAME = "demo";
|
|
24
|
-
const TASK_ID = "00000000-0000-4000-8000-000000000001";
|
|
25
|
-
const CONVERSATION_ID = "00000000-0000-4000-8000-00000000000c";
|
|
26
|
-
const MESSAGE_ID = "00000000-0000-4000-8000-00000000000a";
|
|
27
|
-
const INITIATOR_ID = "00000000-0000-4000-8000-0000000000f0";
|
|
28
|
-
const BOB_AGENT_ID = "00000000-0000-4000-8000-000000000ab1";
|
|
29
|
-
const CAROL_AGENT_ID = "00000000-0000-4000-8000-000000000ab2";
|
|
30
|
-
const DAVE_AGENT_ID_UUID = "00000000-0000-4000-8000-000000000ab3";
|
|
31
|
-
const NOW_ISO = "2026-05-19T00:00:00Z";
|
|
32
|
-
const VALID_APP_ID_OVERRIDE = "11111111-2222-4333-8444-555555555555";
|
|
33
|
-
const UUID_V1_OVERRIDE = "11111111-2222-1333-8444-555555555555";
|
|
34
|
-
const taskFixture = {
|
|
35
|
-
id: TASK_ID,
|
|
36
|
-
appId: DEFAULT_APP_ID,
|
|
37
|
-
initiatorAgentId: INITIATOR_ID,
|
|
38
|
-
status: "active",
|
|
39
|
-
startedAt: NOW_ISO,
|
|
40
|
-
endedAt: null,
|
|
41
|
-
createdAt: NOW_ISO,
|
|
42
|
-
};
|
|
43
|
-
const conversationFixture = {
|
|
44
|
-
id: CONVERSATION_ID,
|
|
45
|
-
name: CONVERSATION_NAME,
|
|
46
|
-
createdBy: INITIATOR_ID,
|
|
47
|
-
createdAt: NOW_ISO,
|
|
48
|
-
updatedAt: NOW_ISO,
|
|
49
|
-
};
|
|
50
|
-
const messageFixture = {
|
|
51
|
-
id: MESSAGE_ID,
|
|
52
|
-
conversationId: CONVERSATION_ID,
|
|
53
|
-
senderId: INITIATOR_ID,
|
|
54
|
-
parts: [{ type: "text", text: "hello" }],
|
|
55
|
-
createdAt: NOW_ISO,
|
|
56
|
-
};
|
|
57
|
-
const agentCard = (id, name) => ({
|
|
58
|
-
id,
|
|
59
|
-
name,
|
|
60
|
-
status: "active",
|
|
61
|
-
});
|
|
62
|
-
const TASK_CREATE_OK = () => ({
|
|
63
|
-
task: taskFixture,
|
|
64
|
-
conversation: conversationFixture,
|
|
65
|
-
});
|
|
66
|
-
const MESSAGES_SEND_OK = () => ({ message: messageFixture });
|
|
67
|
-
const respondLookup = (call, config) => {
|
|
68
|
-
// Batched lookup (P3-2): coalesce all requested-name agent cards from
|
|
69
|
-
// the mock's `lookupResults` map. Names with no entry return nothing
|
|
70
|
-
// — the handler maps that to `UnresolvedParticipantError` per token.
|
|
71
|
-
const params = call.params;
|
|
72
|
-
const lookupResults = config.lookupResults ?? {};
|
|
73
|
-
const agents = params.names.flatMap((name) => lookupResults[name] ?? []);
|
|
74
|
-
return { agents };
|
|
75
|
-
};
|
|
76
|
-
const respondFromConfig = (config) => (call) => {
|
|
77
|
-
if (call.method === AgentsLookupByName.name) {
|
|
78
|
-
return respondLookup(call, config);
|
|
79
|
-
}
|
|
80
|
-
if (call.method === TaskRequest.name) {
|
|
81
|
-
return (config.taskCreate ?? TASK_CREATE_OK)();
|
|
82
|
-
}
|
|
83
|
-
if (call.method === MessagesSend.name) {
|
|
84
|
-
return (config.messagesSend ?? MESSAGES_SEND_OK)();
|
|
85
|
-
}
|
|
86
|
-
if (call.method === TaskConversationList.name) {
|
|
87
|
-
return (config.taskConversationList ?? (() => ({ items: [] })))(call);
|
|
88
|
-
}
|
|
89
|
-
return new Error(`Unexpected RPC: ${call.method}`);
|
|
90
|
-
};
|
|
91
|
-
const makeTransportWith = (config) => makeFakeTransport(respondFromConfig(config));
|
|
92
|
-
const runWith = (transport, args) => runStartHandler(args).pipe(Effect.provideService(Transport, transport));
|
|
93
|
-
const findCallOf = (calls, method) => calls.find((c) => c.method === method);
|
|
94
|
-
const taskCreateParams = (calls) => findCallOf(calls, TaskRequest.name).params;
|
|
95
|
-
const mutatingCalls = (calls) => calls.filter((c) => c.method === TaskRequest.name || c.method === MessagesSend.name);
|
|
96
|
-
const standardArgs = (participants, overrides = {}) => ({
|
|
97
|
-
name: CONVERSATION_NAME,
|
|
98
|
-
participants,
|
|
99
|
-
message: undefined,
|
|
100
|
-
appId: undefined,
|
|
101
|
-
...overrides,
|
|
102
|
-
});
|
|
103
|
-
const onlyBobLookup = () => ({
|
|
104
|
-
lookupResults: { bob: [agentCard(BOB_AGENT_ID, "bob")] },
|
|
105
|
-
});
|
|
106
|
-
const bobCarolLookup = () => ({
|
|
107
|
-
lookupResults: {
|
|
108
|
-
bob: [agentCard(BOB_AGENT_ID, "bob")],
|
|
109
|
-
carol: [agentCard(CAROL_AGENT_ID, "carol")],
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
// ─── Shared harness (console + process.exit) ──────────────────────────────
|
|
113
|
-
let stdoutSpy;
|
|
114
|
-
let stderrSpy;
|
|
115
|
-
let exitSpy;
|
|
116
|
-
const originalExit = process.exit;
|
|
117
|
-
const installHarness = () => {
|
|
118
|
-
stdoutSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
119
|
-
stderrSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
120
|
-
exitSpy = vi.fn();
|
|
121
|
-
process.exit = exitSpy;
|
|
122
|
-
};
|
|
123
|
-
const restoreHarness = () => {
|
|
124
|
-
process.exit = originalExit;
|
|
125
|
-
stdoutSpy.mockRestore();
|
|
126
|
-
stderrSpy.mockRestore();
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* Single-source the install/restore lifecycle for one `body` execution.
|
|
130
|
-
* Used by property-test iterators (`expectNonV4Rejected`,
|
|
131
|
-
* `expectNonAgentTokenRejected`) so each `fc.assert` iteration gets a
|
|
132
|
-
* fresh, isolated harness without inline `installHarness()` / explicit
|
|
133
|
-
* `restoreHarness()` calls that drift out of sync over time. `finally`
|
|
134
|
-
* guarantees restore even on `expect` throw.
|
|
135
|
-
*/
|
|
136
|
-
const withInstalledHarness = (body) => {
|
|
137
|
-
installHarness();
|
|
138
|
-
try {
|
|
139
|
-
body();
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
restoreHarness();
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
const stderrLines = () => stderrSpy.mock.calls.map((c) => String(c[0] ?? ""));
|
|
146
|
-
// ─── Test bodies: participant model ───────────────────────────────────────
|
|
147
|
-
const dmShape = () => Effect.gen(function* () {
|
|
148
|
-
const { calls, transport } = makeTransportWith(onlyBobLookup());
|
|
149
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
150
|
-
const params = taskCreateParams(calls);
|
|
151
|
-
expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID]);
|
|
152
|
-
expect(params.invitedAgentIds.length).toBe(1);
|
|
153
|
-
expect(params.initialConversation.participants).toEqual([BOB_AGENT_ID]);
|
|
154
|
-
expect(params.initialConversation.name).toBe(CONVERSATION_NAME);
|
|
155
|
-
});
|
|
156
|
-
const groupShape = () => Effect.gen(function* () {
|
|
157
|
-
const { calls, transport } = makeTransportWith(bobCarolLookup());
|
|
158
|
-
yield* runWith(transport, standardArgs(["agent:bob", "agent:carol"]));
|
|
159
|
-
const params = taskCreateParams(calls);
|
|
160
|
-
expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID, CAROL_AGENT_ID]);
|
|
161
|
-
// Pins the P3-2 batched-lookup optimization: N name-shaped tokens
|
|
162
|
-
// coalesce into ONE `AgentsLookupByName` RPC, not N. Drift here
|
|
163
|
-
// re-introduces a fan-out per token (visible cost on group tasks).
|
|
164
|
-
const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
|
|
165
|
-
expect(lookups).toHaveLength(1);
|
|
166
|
-
expect(lookups[0].params).toEqual({ names: ["bob", "carol"] });
|
|
167
|
-
});
|
|
168
|
-
const duplicateNameDedupesLookup = () => Effect.gen(function* () {
|
|
169
|
-
// P3-2 secondary invariant: duplicate name tokens dedupe in the
|
|
170
|
-
// batched lookup payload (one wire-name per unique value) but
|
|
171
|
-
// resolve to the agent id at every input position. UUID-shaped
|
|
172
|
-
// siblings still skip the wire.
|
|
173
|
-
const { calls, transport } = makeTransportWith(onlyBobLookup());
|
|
174
|
-
yield* runWith(transport, standardArgs(["agent:bob", `agent:${DAVE_AGENT_ID_UUID}`, "agent:bob"]));
|
|
175
|
-
const params = taskCreateParams(calls);
|
|
176
|
-
expect(params.invitedAgentIds).toEqual([
|
|
177
|
-
BOB_AGENT_ID,
|
|
178
|
-
DAVE_AGENT_ID_UUID,
|
|
179
|
-
BOB_AGENT_ID,
|
|
180
|
-
]);
|
|
181
|
-
const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
|
|
182
|
-
expect(lookups).toHaveLength(1);
|
|
183
|
-
expect(lookups[0].params).toEqual({ names: ["bob"] });
|
|
184
|
-
});
|
|
185
|
-
const callerExcluded = () => Effect.gen(function* () {
|
|
186
|
-
const { calls, transport } = makeTransportWith(onlyBobLookup());
|
|
187
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
188
|
-
const params = taskCreateParams(calls);
|
|
189
|
-
expect(params.invitedAgentIds).not.toContain(INITIATOR_ID);
|
|
190
|
-
expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID]);
|
|
191
|
-
});
|
|
192
|
-
const uuidShorthand = () => Effect.gen(function* () {
|
|
193
|
-
const { calls, transport } = makeTransportWith({});
|
|
194
|
-
yield* runWith(transport, standardArgs([`agent:${DAVE_AGENT_ID_UUID}`]));
|
|
195
|
-
const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
|
|
196
|
-
expect(lookups).toEqual([]);
|
|
197
|
-
const params = taskCreateParams(calls);
|
|
198
|
-
expect(params.invitedAgentIds).toEqual([DAVE_AGENT_ID_UUID]);
|
|
199
|
-
});
|
|
200
|
-
const zeroParticipants = () => Effect.gen(function* () {
|
|
201
|
-
const { calls, transport } = makeTransportWith({});
|
|
202
|
-
yield* runWith(transport, standardArgs([]));
|
|
203
|
-
const params = taskCreateParams(calls);
|
|
204
|
-
expect(params.invitedAgentIds).toEqual([]);
|
|
205
|
-
// P2-B: zero-participant path MUST omit `participants` from
|
|
206
|
-
// `initialConversation` so the wire payload satisfies
|
|
207
|
-
// `InitialConversationSchema` (participants is Optional with
|
|
208
|
-
// `minItems: 1`). The exact shape pin guards against drift.
|
|
209
|
-
expect(params.initialConversation).toEqual({ name: CONVERSATION_NAME });
|
|
210
|
-
expect("participants" in params.initialConversation).toBe(false);
|
|
211
|
-
const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
|
|
212
|
-
expect(lookups).toEqual([]);
|
|
213
|
-
});
|
|
214
|
-
describe("moltzap start — participant model", () => {
|
|
215
|
-
beforeEach(installHarness);
|
|
216
|
-
afterEach(restoreHarness);
|
|
217
|
-
it("dm-shape", dmShape);
|
|
218
|
-
it("group-shape, order preserved", groupShape);
|
|
219
|
-
it("caller-excluded", callerExcluded);
|
|
220
|
-
it("uuid-shorthand", uuidShorthand);
|
|
221
|
-
it("zero-participants admitted (plan §R4)", zeroParticipants);
|
|
222
|
-
it("duplicate name tokens dedupe in batched lookup (P3-2)", duplicateNameDedupesLookup);
|
|
223
|
-
});
|
|
224
|
-
// ─── Test bodies: output format ───────────────────────────────────────────
|
|
225
|
-
const outputFormat = () => Effect.gen(function* () {
|
|
226
|
-
const { transport } = makeTransportWith(onlyBobLookup());
|
|
227
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
|
|
228
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${TASK_ID} (conversation: ${CONVERSATION_ID})`);
|
|
229
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Message sent: ${MESSAGE_ID}`);
|
|
230
|
-
});
|
|
231
|
-
describe("moltzap start — output format", () => {
|
|
232
|
-
beforeEach(installHarness);
|
|
233
|
-
afterEach(restoreHarness);
|
|
234
|
-
it("prints exact stdout strings with response IDs", outputFormat);
|
|
235
|
-
});
|
|
236
|
-
// ─── Test bodies: --app-id ────────────────────────────────────────────────
|
|
237
|
-
const defaultAppId = () => Effect.gen(function* () {
|
|
238
|
-
const { calls, transport } = makeTransportWith(onlyBobLookup());
|
|
239
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
240
|
-
expect(taskCreateParams(calls).appId).toBe(DEFAULT_APP_ID);
|
|
241
|
-
});
|
|
242
|
-
const validAppIdOverride = () => Effect.gen(function* () {
|
|
243
|
-
const { calls, transport } = makeTransportWith(onlyBobLookup());
|
|
244
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { appId: VALID_APP_ID_OVERRIDE }));
|
|
245
|
-
expect(taskCreateParams(calls).appId).toBe(VALID_APP_ID_OVERRIDE);
|
|
246
|
-
});
|
|
247
|
-
const invalidAppIdGarbage = () => Effect.gen(function* () {
|
|
248
|
-
const { calls, transport } = makeTransportWith({});
|
|
249
|
-
yield* runWith(transport, standardArgs([], { appId: "not-a-uuid" }));
|
|
250
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
251
|
-
expect(calls).toEqual([]);
|
|
252
|
-
expect(stderrSpy).toHaveBeenCalledWith("Invalid --app-id: not a UUID");
|
|
253
|
-
});
|
|
254
|
-
const invalidAppIdUuidV1 = () => Effect.gen(function* () {
|
|
255
|
-
const { calls, transport } = makeTransportWith({});
|
|
256
|
-
yield* runWith(transport, standardArgs([], { appId: UUID_V1_OVERRIDE }));
|
|
257
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
258
|
-
expect(calls).toEqual([]);
|
|
259
|
-
});
|
|
260
|
-
const nonV4HexDigit = fc
|
|
261
|
-
.integer({ min: 0, max: 15 })
|
|
262
|
-
.filter((n) => n !== 4)
|
|
263
|
-
.map((n) => n.toString(16));
|
|
264
|
-
/**
|
|
265
|
-
* Property body: builds a syntactically-valid UUID with a non-4
|
|
266
|
-
* version digit and asserts the handler exits 64 with zero RPC calls.
|
|
267
|
-
* Runs synchronously via `Effect.runSync` because the test transport
|
|
268
|
-
* never suspends.
|
|
269
|
-
*/
|
|
270
|
-
const expectNonV4Rejected = (digit) => withInstalledHarness(() => {
|
|
271
|
-
const uuid = `11111111-2222-${digit}333-8444-555555555555`;
|
|
272
|
-
const { calls, transport } = makeTransportWith({});
|
|
273
|
-
Effect.runSync(runWith(transport, standardArgs([], { appId: uuid })));
|
|
274
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
275
|
-
expect(calls).toEqual([]);
|
|
276
|
-
});
|
|
277
|
-
const invariantNonV4Rejection = () => {
|
|
278
|
-
fc.assert(fc.property(nonV4HexDigit, expectNonV4Rejected), { numRuns: 30 });
|
|
279
|
-
// Re-install for afterEach symmetry; each property iteration tore
|
|
280
|
-
// down its harness via `withInstalledHarness`, leaving none for the
|
|
281
|
-
// outer afterEach to restore.
|
|
282
|
-
installHarness();
|
|
283
|
-
};
|
|
284
|
-
describe("moltzap start — --app-id", () => {
|
|
285
|
-
beforeEach(installHarness);
|
|
286
|
-
afterEach(restoreHarness);
|
|
287
|
-
it("default sends DEFAULT_APP_ID", defaultAppId);
|
|
288
|
-
it("override sends the provided v4", validAppIdOverride);
|
|
289
|
-
it("garbage UUID -> exit 64, ZERO RPC calls", invalidAppIdGarbage);
|
|
290
|
-
it("UUID v1 -> exit 64, ZERO RPC calls", invalidAppIdUuidV1);
|
|
291
|
-
plainIt("invariant: every non-v4 syntax exits 64 with ZERO RPC calls", invariantNonV4Rejection);
|
|
292
|
-
});
|
|
293
|
-
// ─── Test bodies: failure paths ───────────────────────────────────────────
|
|
294
|
-
const serverRejectsTaskCreate = () => Effect.gen(function* () {
|
|
295
|
-
const { transport } = makeTransportWith({
|
|
296
|
-
...onlyBobLookup(),
|
|
297
|
-
taskCreate: () => new Error("app not installed"),
|
|
298
|
-
});
|
|
299
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { appId: VALID_APP_ID_OVERRIDE }));
|
|
300
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
301
|
-
expect(stderrLines().some((s) => s.startsWith("Failed: "))).toBe(true);
|
|
302
|
-
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
303
|
-
});
|
|
304
|
-
const partialSuccess = () => Effect.gen(function* () {
|
|
305
|
-
const { transport } = makeTransportWith({
|
|
306
|
-
...onlyBobLookup(),
|
|
307
|
-
messagesSend: () => new Error("conv full"),
|
|
308
|
-
});
|
|
309
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
|
|
310
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${TASK_ID} (conversation: ${CONVERSATION_ID})`);
|
|
311
|
-
expect(stderrLines().some((s) => s.startsWith("Error sending message: "))).toBe(true);
|
|
312
|
-
expect(exitSpy).toHaveBeenCalledWith(2);
|
|
313
|
-
});
|
|
314
|
-
const unresolvedLookupEmpty = () => Effect.gen(function* () {
|
|
315
|
-
const { calls, transport } = makeTransportWith({ lookupResults: {} });
|
|
316
|
-
yield* runWith(transport, standardArgs(["agent:unknown"]));
|
|
317
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
318
|
-
expect(mutatingCalls(calls)).toEqual([]);
|
|
319
|
-
expect(stderrLines().some((s) => s.includes("agent:unknown"))).toBe(true);
|
|
320
|
-
});
|
|
321
|
-
const unresolvedShape = () => Effect.gen(function* () {
|
|
322
|
-
const { calls, transport } = makeTransportWith({});
|
|
323
|
-
yield* runWith(transport, standardArgs(["bob"]));
|
|
324
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
325
|
-
expect(calls).toEqual([]);
|
|
326
|
-
});
|
|
327
|
-
const tooManyDistinctNames = () => Effect.gen(function* () {
|
|
328
|
-
// P3-2 carve-out: `AgentsLookupByName.params.names` is capped at
|
|
329
|
-
// `maxItems: 100`. With 101 DISTINCT name tokens, the batched RPC
|
|
330
|
-
// would otherwise fail server-side AJV → opaque exit 1. The
|
|
331
|
-
// pre-RPC cap check surfaces it as exit 64 with a clear message.
|
|
332
|
-
const tokens = Array.from({ length: 101 }, (_, i) => `agent:user${i.toString().padStart(3, "0")}`);
|
|
333
|
-
const { calls, transport } = makeTransportWith({});
|
|
334
|
-
yield* runWith(transport, standardArgs(tokens));
|
|
335
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
336
|
-
// Cap fires BEFORE the RPC — zero wire calls.
|
|
337
|
-
expect(calls).toEqual([]);
|
|
338
|
-
// Structural assertion: diagnostic mentions BOTH the offending
|
|
339
|
-
// count and the schema cap so the user can reduce input directly.
|
|
340
|
-
expect(stderrLines().some((s) => s.includes("Too many") && s.includes("101") && s.includes("100"))).toBe(true);
|
|
341
|
-
});
|
|
342
|
-
/**
|
|
343
|
-
* Property body: any token without the `agent:` prefix fails at the
|
|
344
|
-
* shape check before any RPC. The handler must always exit 64 with
|
|
345
|
-
* zero RPC calls — the resolver short-circuits at the prefix test.
|
|
346
|
-
*/
|
|
347
|
-
const expectNonAgentTokenRejected = (token) => withInstalledHarness(() => {
|
|
348
|
-
const { calls, transport } = makeTransportWith({});
|
|
349
|
-
Effect.runSync(runWith(transport, standardArgs([token])));
|
|
350
|
-
expect(exitSpy).toHaveBeenCalledWith(64);
|
|
351
|
-
expect(calls).toEqual([]);
|
|
352
|
-
});
|
|
353
|
-
const nonAgentToken = fc
|
|
354
|
-
.string({ minLength: 1, maxLength: 30 })
|
|
355
|
-
.filter((s) => !s.startsWith("agent:"));
|
|
356
|
-
const invariantNonAgentRejection = () => {
|
|
357
|
-
fc.assert(fc.property(nonAgentToken, expectNonAgentTokenRejected), {
|
|
358
|
-
numRuns: 30,
|
|
359
|
-
});
|
|
360
|
-
installHarness();
|
|
361
|
-
};
|
|
362
|
-
describe("moltzap start — failure paths", () => {
|
|
363
|
-
beforeEach(installHarness);
|
|
364
|
-
afterEach(restoreHarness);
|
|
365
|
-
it("server-reject TaskRequest -> exit 1", serverRejectsTaskCreate);
|
|
366
|
-
it("partial-success TaskRequest OK + MessagesSend fail -> exit 2", partialSuccess);
|
|
367
|
-
it("unresolved (lookup empty) -> exit 64, ZERO mutating RPCs", unresolvedLookupEmpty);
|
|
368
|
-
it("unresolved (shape) -> exit 64, ZERO RPC calls", unresolvedShape);
|
|
369
|
-
it(">100 distinct names -> exit 64 with TooManyParticipantNamesError diagnostic", tooManyDistinctNames);
|
|
370
|
-
plainIt("invariant: non-agent: prefix tokens exit 64 with ZERO RPC calls", invariantNonAgentRejection);
|
|
371
|
-
});
|
|
372
|
-
// ─── Test bodies: dedup hit (P2-A, spec D2 amendment N6) ─────────────────
|
|
373
|
-
const EXISTING_TASK_ID = "00000000-0000-4000-8000-0000000000d1";
|
|
374
|
-
const EXISTING_CONV_ID_ACTIVE = "00000000-0000-4000-8000-0000000000d2";
|
|
375
|
-
const EXISTING_CONV_ID_OLDER = "00000000-0000-4000-8000-0000000000d3";
|
|
376
|
-
const OTHER_TASK_ID = "00000000-0000-4000-8000-0000000000e9";
|
|
377
|
-
const existingTaskFixture = {
|
|
378
|
-
...taskFixture,
|
|
379
|
-
id: EXISTING_TASK_ID,
|
|
380
|
-
};
|
|
381
|
-
const existingTaskClosedFixture = {
|
|
382
|
-
...existingTaskFixture,
|
|
383
|
-
status: "closed",
|
|
384
|
-
endedAt: NOW_ISO,
|
|
385
|
-
};
|
|
386
|
-
const conversationFixtureActive = {
|
|
387
|
-
...conversationFixture,
|
|
388
|
-
id: EXISTING_CONV_ID_ACTIVE,
|
|
389
|
-
createdAt: "2026-05-19T01:00:00Z",
|
|
390
|
-
};
|
|
391
|
-
const conversationFixtureOlder = {
|
|
392
|
-
...conversationFixture,
|
|
393
|
-
id: EXISTING_CONV_ID_OLDER,
|
|
394
|
-
createdAt: "2026-05-18T00:00:00Z",
|
|
395
|
-
};
|
|
396
|
-
const TASK_CREATE_DEDUP = (task = existingTaskFixture) => () => ({
|
|
397
|
-
task,
|
|
398
|
-
conversation: null,
|
|
399
|
-
});
|
|
400
|
-
const taskConvListItem = (taskId, conversation) => ({
|
|
401
|
-
taskId,
|
|
402
|
-
conversation,
|
|
403
|
-
participants: [INITIATOR_ID, BOB_AGENT_ID],
|
|
404
|
-
});
|
|
405
|
-
const dedupHitSingleConversation = () => Effect.gen(function* () {
|
|
406
|
-
const { calls, transport } = makeTransportWith({
|
|
407
|
-
...onlyBobLookup(),
|
|
408
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
409
|
-
taskConversationList: () => ({
|
|
410
|
-
items: [taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive)],
|
|
411
|
-
}),
|
|
412
|
-
});
|
|
413
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
414
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
|
|
415
|
-
expect(exitSpy).not.toHaveBeenCalled();
|
|
416
|
-
// Must have called TaskConversationList exactly once (single page,
|
|
417
|
-
// single match). Cursor MUST NOT be passed on first page.
|
|
418
|
-
const listCall = findCallOf(calls, TaskConversationList.name);
|
|
419
|
-
expect(listCall).toBeDefined();
|
|
420
|
-
expect(listCall.params).toEqual({ limit: 100 });
|
|
421
|
-
});
|
|
422
|
-
const dedupHitMultipleConversations = () => Effect.gen(function* () {
|
|
423
|
-
// Server-side ordering is activity-desc, so the FIRST item under
|
|
424
|
-
// the target task is the most-recently-active. The handler picks
|
|
425
|
-
// it without re-sorting (per spec D2 amendment N6 tie-break: "first
|
|
426
|
-
// match in server iteration order").
|
|
427
|
-
const { transport } = makeTransportWith({
|
|
428
|
-
...onlyBobLookup(),
|
|
429
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
430
|
-
taskConversationList: () => ({
|
|
431
|
-
items: [
|
|
432
|
-
// Server order: active first, older second.
|
|
433
|
-
taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
|
|
434
|
-
taskConvListItem(EXISTING_TASK_ID, conversationFixtureOlder),
|
|
435
|
-
],
|
|
436
|
-
}),
|
|
437
|
-
});
|
|
438
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
439
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
|
|
440
|
-
expect(exitSpy).not.toHaveBeenCalled();
|
|
441
|
-
});
|
|
442
|
-
const dedupHitWithMessage = () => Effect.gen(function* () {
|
|
443
|
-
const { calls, transport } = makeTransportWith({
|
|
444
|
-
...onlyBobLookup(),
|
|
445
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
446
|
-
taskConversationList: () => ({
|
|
447
|
-
items: [taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive)],
|
|
448
|
-
}),
|
|
449
|
-
});
|
|
450
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
|
|
451
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
|
|
452
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Message sent: ${MESSAGE_ID}`);
|
|
453
|
-
// The MessagesSend payload MUST route to the REUSED conversation,
|
|
454
|
-
// not the (null) freshly-created one.
|
|
455
|
-
const sendCall = findCallOf(calls, MessagesSend.name);
|
|
456
|
-
expect(sendCall).toBeDefined();
|
|
457
|
-
expect(sendCall.params.conversationId).toBe(EXISTING_CONV_ID_ACTIVE);
|
|
458
|
-
});
|
|
459
|
-
const dedupHitFiltersOtherTaskAndArchived = () => Effect.gen(function* () {
|
|
460
|
-
// The list mixes items from other tasks AND archived rows under
|
|
461
|
-
// our task — the handler must skip both and find the lone non-
|
|
462
|
-
// archived match.
|
|
463
|
-
const archivedConv = {
|
|
464
|
-
...conversationFixtureOlder,
|
|
465
|
-
archivedAt: NOW_ISO,
|
|
466
|
-
};
|
|
467
|
-
const { transport } = makeTransportWith({
|
|
468
|
-
...onlyBobLookup(),
|
|
469
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
470
|
-
taskConversationList: () => ({
|
|
471
|
-
items: [
|
|
472
|
-
taskConvListItem(OTHER_TASK_ID, conversationFixtureActive),
|
|
473
|
-
taskConvListItem(EXISTING_TASK_ID, archivedConv),
|
|
474
|
-
taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
|
|
475
|
-
],
|
|
476
|
-
}),
|
|
477
|
-
});
|
|
478
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
479
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
|
|
480
|
-
});
|
|
481
|
-
const dedupHitTaskClosedNoUsableConversation = () => Effect.gen(function* () {
|
|
482
|
-
// Existing task is reported by the dedup query but all
|
|
483
|
-
// conversations under it are archived → `findReusableConversation`
|
|
484
|
-
// returns null → handler emits closed-task diagnostic + exit 1.
|
|
485
|
-
const archivedConv = {
|
|
486
|
-
...conversationFixtureActive,
|
|
487
|
-
archivedAt: NOW_ISO,
|
|
488
|
-
};
|
|
489
|
-
const { transport } = makeTransportWith({
|
|
490
|
-
...onlyBobLookup(),
|
|
491
|
-
taskCreate: TASK_CREATE_DEDUP(existingTaskClosedFixture),
|
|
492
|
-
taskConversationList: () => ({
|
|
493
|
-
items: [taskConvListItem(EXISTING_TASK_ID, archivedConv)],
|
|
494
|
-
}),
|
|
495
|
-
});
|
|
496
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
497
|
-
expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
|
|
498
|
-
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
499
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
500
|
-
});
|
|
501
|
-
const dedupHitPaginatesUntilFound = () => Effect.gen(function* () {
|
|
502
|
-
// First page is full of OTHER tasks → handler must follow
|
|
503
|
-
// `nextCursor` to find the target on page two. Pin both calls so a
|
|
504
|
-
// regression that drops pagination support fails the test.
|
|
505
|
-
let callCount = 0;
|
|
506
|
-
const { calls, transport } = makeTransportWith({
|
|
507
|
-
...onlyBobLookup(),
|
|
508
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
509
|
-
taskConversationList: () => {
|
|
510
|
-
callCount += 1;
|
|
511
|
-
if (callCount === 1) {
|
|
512
|
-
return {
|
|
513
|
-
items: [taskConvListItem(OTHER_TASK_ID, conversationFixtureOlder)],
|
|
514
|
-
nextCursor: "2026-05-18T12:00:00Z",
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
return {
|
|
518
|
-
items: [
|
|
519
|
-
taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
|
|
520
|
-
],
|
|
521
|
-
};
|
|
522
|
-
},
|
|
523
|
-
});
|
|
524
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
525
|
-
expect(callCount).toBe(2);
|
|
526
|
-
expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
|
|
527
|
-
// Second call MUST forward the cursor returned by the first page.
|
|
528
|
-
const listCalls = calls.filter((c) => c.method === TaskConversationList.name);
|
|
529
|
-
expect(listCalls).toHaveLength(2);
|
|
530
|
-
expect(listCalls[1].params).toEqual({
|
|
531
|
-
limit: 100,
|
|
532
|
-
cursor: "2026-05-18T12:00:00Z",
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
const dedupHitCapsAtMaxPages = () => Effect.gen(function* () {
|
|
536
|
-
// Adversarial server that NEVER returns nextCursor: undefined. The
|
|
537
|
-
// handler MUST stop at `DEDUP_LOOKUP_MAX_PAGES = 10` and surface
|
|
538
|
-
// the closed-task diagnostic; without the cap this loops forever
|
|
539
|
-
// and the test times out. Pins the cap value as a contract.
|
|
540
|
-
let pageCount = 0;
|
|
541
|
-
const { calls, transport } = makeTransportWith({
|
|
542
|
-
...onlyBobLookup(),
|
|
543
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
544
|
-
taskConversationList: () => {
|
|
545
|
-
pageCount += 1;
|
|
546
|
-
return {
|
|
547
|
-
items: [taskConvListItem(OTHER_TASK_ID, conversationFixtureOlder)],
|
|
548
|
-
nextCursor: `cursor-page-${pageCount}`,
|
|
549
|
-
};
|
|
550
|
-
},
|
|
551
|
-
});
|
|
552
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
553
|
-
const listCalls = calls.filter((c) => c.method === TaskConversationList.name);
|
|
554
|
-
expect(listCalls).toHaveLength(10); // DEDUP_LOOKUP_MAX_PAGES
|
|
555
|
-
expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
|
|
556
|
-
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
557
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
558
|
-
});
|
|
559
|
-
const dedupHitClosedTaskWithMessage = () => Effect.gen(function* () {
|
|
560
|
-
// Partial-failure: TaskRequest dedup-hit + closed-task + --message.
|
|
561
|
-
// The user's message is intentionally DROPPED — handler exits 1
|
|
562
|
-
// before reaching `sendFirstMessage`. Pinned so any future change
|
|
563
|
-
// to add a separate diagnostic ('Message NOT sent: ...') has to
|
|
564
|
-
// update this test, preventing silent regression.
|
|
565
|
-
const archivedConv = {
|
|
566
|
-
...conversationFixtureActive,
|
|
567
|
-
archivedAt: NOW_ISO,
|
|
568
|
-
};
|
|
569
|
-
const { calls, transport } = makeTransportWith({
|
|
570
|
-
...onlyBobLookup(),
|
|
571
|
-
taskCreate: TASK_CREATE_DEDUP(existingTaskClosedFixture),
|
|
572
|
-
taskConversationList: () => ({
|
|
573
|
-
items: [taskConvListItem(EXISTING_TASK_ID, archivedConv)],
|
|
574
|
-
}),
|
|
575
|
-
});
|
|
576
|
-
yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
|
|
577
|
-
expect(calls.filter((c) => c.method === MessagesSend.name)).toEqual([]);
|
|
578
|
-
expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
|
|
579
|
-
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
580
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
581
|
-
});
|
|
582
|
-
const dedupHitListRpcFailure = () => Effect.gen(function* () {
|
|
583
|
-
// TaskRequest succeeds (dedup hit). TaskConversationList fails with
|
|
584
|
-
// a wire error (e.g. cursor-format drift). Without the
|
|
585
|
-
// `DedupListFailedError` remap, the user would see
|
|
586
|
-
// `Failed: <list-error>` and assume TaskRequest failed → retry →
|
|
587
|
-
// re-dedup forever. The remap surfaces the existing taskId so the
|
|
588
|
-
// user knows the task is real.
|
|
589
|
-
const { transport } = makeTransportWith({
|
|
590
|
-
...onlyBobLookup(),
|
|
591
|
-
taskCreate: TASK_CREATE_DEDUP(),
|
|
592
|
-
taskConversationList: () => new Error("cursor server-rejected"),
|
|
593
|
-
});
|
|
594
|
-
yield* runWith(transport, standardArgs(["agent:bob"]));
|
|
595
|
-
expect(stderrLines().some((s) => s.startsWith(`Task ${EXISTING_TASK_ID} already exists`) &&
|
|
596
|
-
s.includes("reusable-conversation lookup failed"))).toBe(true);
|
|
597
|
-
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
598
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
599
|
-
});
|
|
600
|
-
describe("moltzap start — dedup hit (P2-A / spec D2 amendment N6)", () => {
|
|
601
|
-
beforeEach(installHarness);
|
|
602
|
-
afterEach(restoreHarness);
|
|
603
|
-
it("single existing conversation -> reuses it", dedupHitSingleConversation);
|
|
604
|
-
it("multiple conversations -> picks first in server iteration order", dedupHitMultipleConversations);
|
|
605
|
-
it("with --message -> sends to reused conversation", dedupHitWithMessage);
|
|
606
|
-
it("filters items from other tasks and archived rows", dedupHitFiltersOtherTaskAndArchived);
|
|
607
|
-
it("task closed / all archived -> exit 1 with stderr diagnostic", dedupHitTaskClosedNoUsableConversation);
|
|
608
|
-
it("paginates via nextCursor when first page misses", dedupHitPaginatesUntilFound);
|
|
609
|
-
it("caps at DEDUP_LOOKUP_MAX_PAGES when server never returns final page", dedupHitCapsAtMaxPages);
|
|
610
|
-
it("closed task + --message -> message dropped, exit 1", dedupHitClosedTaskWithMessage);
|
|
611
|
-
it("TaskConversationList wire failure -> distinct diagnostic, NOT 'Failed: ...'", dedupHitListRpcFailure);
|
|
612
|
-
});
|
|
613
|
-
// ─── Tests: help text ─────────────────────────────────────────────────────
|
|
614
|
-
const helpDescription = () => {
|
|
615
|
-
const desc = JSON.stringify(startCommand);
|
|
616
|
-
expect(desc).toContain(HELP_SYNOPSIS);
|
|
617
|
-
expect(desc).toContain(HELP_MENTIONS_MESSAGES_SEND);
|
|
618
|
-
expect(desc).toContain(HELP_APP_ID_FLAG);
|
|
619
|
-
expect(desc).toContain(HELP_EXIT_CODES_HEADING);
|
|
620
|
-
};
|
|
621
|
-
describe("moltzap start — help", () => {
|
|
622
|
-
plainIt("Command.withDescription contains synopsis + --app-id + exit-code table", helpDescription);
|
|
623
|
-
});
|
|
624
|
-
//# sourceMappingURL=start.test.js.map
|