@namzu/sdk 0.2.0 → 0.4.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/CHANGELOG.md +74 -2
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +3 -2
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +5 -2
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/bridge/a2a/index.d.ts +1 -1
- package/dist/bridge/a2a/index.d.ts.map +1 -1
- package/dist/bridge/a2a/index.js +1 -1
- package/dist/bridge/a2a/index.js.map +1 -1
- package/dist/bridge/a2a/message.d.ts +0 -2
- package/dist/bridge/a2a/message.d.ts.map +1 -1
- package/dist/bridge/a2a/message.js +0 -26
- package/dist/bridge/a2a/message.js.map +1 -1
- package/dist/bridge/a2a/task.d.ts +4 -3
- package/dist/bridge/a2a/task.d.ts.map +1 -1
- package/dist/bridge/a2a/task.js +4 -4
- package/dist/bridge/a2a/task.js.map +1 -1
- package/dist/contracts/api.d.ts +6 -38
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/contracts/ids.d.ts +1 -1
- package/dist/contracts/ids.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +3 -5
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/schemas.d.ts +1 -31
- package/dist/contracts/schemas.d.ts.map +1 -1
- package/dist/contracts/schemas.js +1 -7
- package/dist/contracts/schemas.js.map +1 -1
- package/dist/index.d.ts +2 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/manager/agent/__tests__/lifecycle.test.js +27 -13
- package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +9 -0
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +93 -31
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/index.d.ts +2 -0
- package/dist/manager/index.d.ts.map +1 -1
- package/dist/manager/index.js +1 -0
- package/dist/manager/index.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +3 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +5 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/manager/thread/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/manager/thread/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/manager/thread/__tests__/lifecycle.test.js +216 -0
- package/dist/manager/thread/__tests__/lifecycle.test.js.map +1 -0
- package/dist/manager/thread/lifecycle.d.ts +105 -0
- package/dist/manager/thread/lifecycle.d.ts.map +1 -0
- package/dist/manager/thread/lifecycle.js +186 -0
- package/dist/manager/thread/lifecycle.js.map +1 -0
- package/dist/rag/retriever.js +2 -2
- package/dist/registry/tool/execute.js +1 -1
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/runtime/query/__tests__/context.test.js +8 -7
- package/dist/runtime/query/__tests__/context.test.js.map +1 -1
- package/dist/runtime/query/context-cache.d.ts +3 -3
- package/dist/runtime/query/context-cache.d.ts.map +1 -1
- package/dist/runtime/query/context-cache.js +2 -2
- package/dist/runtime/query/context-cache.js.map +1 -1
- package/dist/runtime/query/context.d.ts +12 -21
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +3 -1
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +13 -15
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +2 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +1 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/session/__tests__/integration/_fixtures.d.ts +11 -4
- package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -1
- package/dist/session/__tests__/integration/_fixtures.js +23 -6
- package/dist/session/__tests__/integration/_fixtures.js.map +1 -1
- package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
- package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
- package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
- package/dist/session/__tests__/integration/capacity-caps.test.js +13 -6
- package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -1
- package/dist/session/__tests__/integration/e2e-spawn.test.js +14 -2
- package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -1
- package/dist/session/__tests__/integration/event-stream-ordering.test.js +14 -7
- package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +26 -14
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +30 -20
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js +25 -9
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +11 -10
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -1
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js +5 -4
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -1
- package/dist/session/__tests__/integration/retention-archive.test.js +3 -2
- package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -1
- package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +2 -1
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/tenant-isolation.test.js +14 -5
- package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -1
- package/dist/session/errors.d.ts +79 -0
- package/dist/session/errors.d.ts.map +1 -1
- package/dist/session/errors.js +57 -0
- package/dist/session/errors.js.map +1 -1
- package/dist/session/handoff/__tests__/broadcast.test.js +49 -31
- package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -1
- package/dist/session/handoff/__tests__/capacity.test.js +21 -18
- package/dist/session/handoff/__tests__/capacity.test.js.map +1 -1
- package/dist/session/handoff/__tests__/single.test.js +39 -30
- package/dist/session/handoff/__tests__/single.test.js.map +1 -1
- package/dist/session/handoff/assignment.d.ts +13 -1
- package/dist/session/handoff/assignment.d.ts.map +1 -1
- package/dist/session/handoff/broadcast.d.ts +7 -0
- package/dist/session/handoff/broadcast.d.ts.map +1 -1
- package/dist/session/handoff/broadcast.js +16 -1
- package/dist/session/handoff/broadcast.js.map +1 -1
- package/dist/session/handoff/single.d.ts +7 -0
- package/dist/session/handoff/single.d.ts.map +1 -1
- package/dist/session/handoff/single.js +13 -1
- package/dist/session/handoff/single.js.map +1 -1
- package/dist/session/hierarchy/__tests__/session.test.js +2 -0
- package/dist/session/hierarchy/__tests__/session.test.js.map +1 -1
- package/dist/session/hierarchy/index.d.ts +1 -0
- package/dist/session/hierarchy/index.d.ts.map +1 -1
- package/dist/session/hierarchy/index.js.map +1 -1
- package/dist/session/hierarchy/session.d.ts +15 -3
- package/dist/session/hierarchy/session.d.ts.map +1 -1
- package/dist/session/hierarchy/session.js.map +1 -1
- package/dist/session/hierarchy/thread.d.ts +54 -0
- package/dist/session/hierarchy/thread.d.ts.map +1 -0
- package/dist/session/hierarchy/thread.js +2 -0
- package/dist/session/hierarchy/thread.js.map +1 -0
- package/dist/session/migration/id-prefix.d.ts +8 -13
- package/dist/session/migration/id-prefix.d.ts.map +1 -1
- package/dist/session/migration/id-prefix.js +8 -13
- package/dist/session/migration/id-prefix.js.map +1 -1
- package/dist/session/retention/__tests__/archive.test.js +3 -2
- package/dist/session/retention/__tests__/archive.test.js.map +1 -1
- package/dist/session/summary/__tests__/materialize.test.js +4 -3
- package/dist/session/summary/__tests__/materialize.test.js.map +1 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +0 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/session/__tests__/disk.test.js +32 -5
- package/dist/store/session/__tests__/disk.test.js.map +1 -1
- package/dist/store/session/__tests__/memory.test.js +50 -9
- package/dist/store/session/__tests__/memory.test.js.map +1 -1
- package/dist/store/session/disk.d.ts +2 -1
- package/dist/store/session/disk.d.ts.map +1 -1
- package/dist/store/session/disk.js +61 -0
- package/dist/store/session/disk.js.map +1 -1
- package/dist/store/session/index.d.ts.map +1 -1
- package/dist/store/session/index.js +3 -4
- package/dist/store/session/index.js.map +1 -1
- package/dist/store/session/memory.d.ts +2 -1
- package/dist/store/session/memory.d.ts.map +1 -1
- package/dist/store/session/memory.js +13 -0
- package/dist/store/session/memory.js.map +1 -1
- package/dist/store/thread/disk.d.ts +41 -0
- package/dist/store/thread/disk.d.ts.map +1 -0
- package/dist/store/thread/disk.js +229 -0
- package/dist/store/thread/disk.js.map +1 -0
- package/dist/store/thread/index.d.ts +4 -0
- package/dist/store/thread/index.d.ts.map +1 -0
- package/dist/store/thread/index.js +6 -0
- package/dist/store/thread/index.js.map +1 -0
- package/dist/store/thread/memory.d.ts +23 -0
- package/dist/store/thread/memory.d.ts.map +1 -0
- package/dist/store/thread/memory.js +90 -0
- package/dist/store/thread/memory.js.map +1 -0
- package/dist/telemetry/runtime-accessors.d.ts +4 -0
- package/dist/telemetry/runtime-accessors.d.ts.map +1 -0
- package/dist/telemetry/runtime-accessors.js +17 -0
- package/dist/telemetry/runtime-accessors.js.map +1 -0
- package/dist/types/agent/base.d.ts +17 -21
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/factory.d.ts +8 -2
- package/dist/types/agent/factory.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +18 -11
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/ids/index.d.ts +5 -9
- package/dist/types/ids/index.d.ts.map +1 -1
- package/dist/types/ids/index.js +4 -4
- package/dist/types/ids/index.js.map +1 -1
- package/dist/types/rag/retrieval.d.ts +4 -3
- package/dist/types/rag/retrieval.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +6 -5
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/metadata.d.ts +5 -18
- package/dist/types/run/metadata.d.ts.map +1 -1
- package/dist/types/session/ids.d.ts +4 -13
- package/dist/types/session/ids.d.ts.map +1 -1
- package/dist/types/session/ids.js +3 -6
- package/dist/types/session/ids.js.map +1 -1
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/store.d.ts +32 -10
- package/dist/types/session/store.d.ts.map +1 -1
- package/dist/types/session/store.js +3 -8
- package/dist/types/session/store.js.map +1 -1
- package/dist/types/thread/index.d.ts +2 -0
- package/dist/types/thread/index.d.ts.map +1 -0
- package/dist/types/thread/index.js +5 -0
- package/dist/types/thread/index.js.map +1 -0
- package/dist/types/thread/store.d.ts +86 -0
- package/dist/types/thread/store.d.ts.map +1 -0
- package/dist/types/thread/store.js +22 -0
- package/dist/types/thread/store.js.map +1 -0
- package/dist/utils/id.d.ts +1 -12
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +3 -23
- package/dist/utils/id.js.map +1 -1
- package/package.json +11 -20
- package/src/agents/ReactiveAgent.ts +3 -2
- package/src/agents/SupervisorAgent.ts +5 -2
- package/src/bridge/a2a/index.ts +0 -1
- package/src/bridge/a2a/message.ts +0 -32
- package/src/bridge/a2a/task.ts +8 -7
- package/src/contracts/api.ts +6 -42
- package/src/contracts/ids.ts +1 -1
- package/src/contracts/index.ts +2 -8
- package/src/contracts/schemas.ts +1 -8
- package/src/index.ts +3 -15
- package/src/manager/agent/__tests__/lifecycle.test.ts +34 -13
- package/src/manager/agent/lifecycle.ts +114 -35
- package/src/manager/index.ts +3 -0
- package/src/manager/run/persistence.ts +7 -1
- package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
- package/src/manager/thread/lifecycle.ts +217 -0
- package/src/rag/retriever.ts +2 -2
- package/src/registry/tool/execute.ts +1 -1
- package/src/runtime/query/__tests__/context.test.ts +9 -8
- package/src/runtime/query/context-cache.ts +4 -4
- package/src/runtime/query/context.ts +15 -22
- package/src/runtime/query/index.ts +16 -17
- package/src/runtime/query/iteration/index.ts +1 -1
- package/src/session/__tests__/integration/_fixtures.ts +36 -8
- package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
- package/src/session/__tests__/integration/capacity-caps.test.ts +13 -6
- package/src/session/__tests__/integration/e2e-spawn.test.ts +20 -2
- package/src/session/__tests__/integration/event-stream-ordering.test.ts +14 -7
- package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +39 -13
- package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +54 -19
- package/src/session/__tests__/integration/handoff-single-e2e.test.ts +40 -9
- package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +13 -10
- package/src/session/__tests__/integration/prev-artifact-dag.test.ts +12 -5
- package/src/session/__tests__/integration/retention-archive.test.ts +5 -3
- package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
- package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +4 -2
- package/src/session/__tests__/integration/tenant-isolation.test.ts +16 -6
- package/src/session/errors.ts +89 -0
- package/src/session/handoff/__tests__/broadcast.test.ts +56 -28
- package/src/session/handoff/__tests__/capacity.test.ts +26 -20
- package/src/session/handoff/__tests__/single.test.ts +45 -28
- package/src/session/handoff/assignment.ts +13 -1
- package/src/session/handoff/broadcast.ts +26 -1
- package/src/session/handoff/single.ts +23 -1
- package/src/session/hierarchy/__tests__/session.test.ts +9 -1
- package/src/session/hierarchy/index.ts +1 -0
- package/src/session/hierarchy/session.ts +15 -3
- package/src/session/hierarchy/thread.ts +55 -0
- package/src/session/migration/id-prefix.ts +8 -13
- package/src/session/retention/__tests__/archive.test.ts +5 -3
- package/src/session/summary/__tests__/materialize.test.ts +6 -4
- package/src/store/index.ts +0 -3
- package/src/store/session/__tests__/disk.test.ts +57 -6
- package/src/store/session/__tests__/memory.test.ts +84 -9
- package/src/store/session/disk.ts +57 -1
- package/src/store/session/index.ts +3 -4
- package/src/store/session/memory.ts +13 -1
- package/src/store/thread/disk.ts +261 -0
- package/src/store/thread/index.ts +7 -0
- package/src/store/thread/memory.ts +104 -0
- package/src/telemetry/runtime-accessors.ts +19 -0
- package/src/types/agent/base.ts +17 -21
- package/src/types/agent/factory.ts +8 -3
- package/src/types/agent/task.ts +19 -11
- package/src/types/ids/index.ts +8 -15
- package/src/types/rag/retrieval.ts +4 -3
- package/src/types/run/config.ts +6 -5
- package/src/types/run/metadata.ts +5 -18
- package/src/types/session/ids.ts +4 -15
- package/src/types/session/index.ts +1 -2
- package/src/types/session/store.ts +34 -11
- package/src/types/thread/index.ts +5 -0
- package/src/types/thread/store.ts +92 -0
- package/src/utils/id.ts +3 -24
- package/dist/provider/telemetry/setup.d.ts +0 -19
- package/dist/provider/telemetry/setup.d.ts.map +0 -1
- package/dist/provider/telemetry/setup.js +0 -102
- package/dist/provider/telemetry/setup.js.map +0 -1
- package/dist/store/conversation/memory.d.ts +0 -43
- package/dist/store/conversation/memory.d.ts.map +0 -1
- package/dist/store/conversation/memory.js +0 -108
- package/dist/store/conversation/memory.js.map +0 -1
- package/dist/telemetry/index.d.ts +0 -6
- package/dist/telemetry/index.d.ts.map +0 -1
- package/dist/telemetry/index.js +0 -4
- package/dist/telemetry/index.js.map +0 -1
- package/dist/telemetry/metrics.d.ts +0 -8
- package/dist/telemetry/metrics.d.ts.map +0 -1
- package/dist/telemetry/metrics.js +0 -53
- package/dist/telemetry/metrics.js.map +0 -1
- package/dist/types/conversation/index.d.ts +0 -14
- package/dist/types/conversation/index.d.ts.map +0 -1
- package/dist/types/conversation/index.js +0 -2
- package/dist/types/conversation/index.js.map +0 -1
- package/dist/types/telemetry/index.d.ts +0 -10
- package/dist/types/telemetry/index.d.ts.map +0 -1
- package/dist/types/telemetry/index.js +0 -2
- package/dist/types/telemetry/index.js.map +0 -1
- package/src/provider/telemetry/setup.ts +0 -125
- package/src/store/conversation/memory.ts +0 -144
- package/src/telemetry/index.ts +0 -14
- package/src/telemetry/metrics.ts +0 -69
- package/src/types/conversation/index.ts +0 -15
- package/src/types/telemetry/index.ts +0 -10
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, expect, it, vi } from 'vitest'
|
|
13
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
13
14
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
15
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
14
16
|
import type { SessionId } from '../../../types/ids/index.js'
|
|
15
|
-
import type { ProjectId } from '../../../types/session/ids.js'
|
|
17
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
16
18
|
import { generateHandoffId } from '../../../utils/id.js'
|
|
17
19
|
import type { HandoffAssignment } from '../../handoff/assignment.js'
|
|
18
20
|
import { type BroadcastHandoffDeps, executeBroadcastHandoff } from '../../handoff/broadcast.js'
|
|
@@ -26,6 +28,7 @@ import { DEFAULT_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
|
|
|
26
28
|
|
|
27
29
|
function buildDeps(
|
|
28
30
|
store: InMemorySessionStore,
|
|
31
|
+
threadStore: InMemoryThreadStore,
|
|
29
32
|
execOverride?: ExecFile,
|
|
30
33
|
): {
|
|
31
34
|
deps: BroadcastHandoffDeps
|
|
@@ -54,6 +57,7 @@ function buildDeps(
|
|
|
54
57
|
workspaceRegistry,
|
|
55
58
|
capacity: new DefaultCapacityValidator(store),
|
|
56
59
|
events: sink,
|
|
60
|
+
threadManager: new ThreadManager({ threadStore, sessionStore: store }),
|
|
57
61
|
},
|
|
58
62
|
events: { ...sink, onBroadcastRollback },
|
|
59
63
|
}
|
|
@@ -62,6 +66,7 @@ function buildDeps(
|
|
|
62
66
|
function buildAssignments(
|
|
63
67
|
sourceSessionId: SessionId,
|
|
64
68
|
projectId: ProjectId,
|
|
69
|
+
threadId: ThreadId,
|
|
65
70
|
recipients: ActorRef[],
|
|
66
71
|
broadcastId = 'bc_integration',
|
|
67
72
|
expectedOwnerVersion = 0,
|
|
@@ -71,6 +76,7 @@ function buildAssignments(
|
|
|
71
76
|
mode: 'broadcast' as const,
|
|
72
77
|
sourceSessionId,
|
|
73
78
|
tenantId: DEFAULT_TENANT,
|
|
79
|
+
threadId,
|
|
74
80
|
projectId,
|
|
75
81
|
sourceActor: userActor('usr_source'),
|
|
76
82
|
recipientActor,
|
|
@@ -83,12 +89,17 @@ function buildAssignments(
|
|
|
83
89
|
describe('Integration — broadcast handoff E2E', () => {
|
|
84
90
|
it('happy: 3-recipient fan-out → each recipient has isolated worktree + source reaches awaiting_merge', async () => {
|
|
85
91
|
const store = new InMemorySessionStore()
|
|
92
|
+
const threadStore = new InMemoryThreadStore()
|
|
86
93
|
const project = await store.createProject(
|
|
87
94
|
{ tenantId: DEFAULT_TENANT, name: 'bc-happy' },
|
|
88
95
|
DEFAULT_TENANT,
|
|
89
96
|
)
|
|
97
|
+
const thread = await threadStore.createThread(
|
|
98
|
+
{ projectId: project.id, title: 'bc-happy' },
|
|
99
|
+
DEFAULT_TENANT,
|
|
100
|
+
)
|
|
90
101
|
const source = await store.createSession(
|
|
91
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
102
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
92
103
|
DEFAULT_TENANT,
|
|
93
104
|
)
|
|
94
105
|
|
|
@@ -103,10 +114,10 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
103
114
|
}
|
|
104
115
|
return okExec()
|
|
105
116
|
}
|
|
106
|
-
const { deps } = buildDeps(store, exec)
|
|
117
|
+
const { deps } = buildDeps(store, threadStore, exec)
|
|
107
118
|
|
|
108
119
|
const recipients = [userActor('usr_bob'), userActor('usr_carol'), userActor('usr_dan')]
|
|
109
|
-
const assignments = buildAssignments(source.id, project.id, recipients)
|
|
120
|
+
const assignments = buildAssignments(source.id, project.id, thread.id, recipients)
|
|
110
121
|
|
|
111
122
|
const outcomes = await executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)
|
|
112
123
|
expect(outcomes).toHaveLength(3)
|
|
@@ -128,12 +139,17 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
128
139
|
|
|
129
140
|
it('rollback on 2nd-recipient provisioning failure: zero orphan records, partialState accurate', async () => {
|
|
130
141
|
const store = new InMemorySessionStore()
|
|
142
|
+
const threadStore = new InMemoryThreadStore()
|
|
131
143
|
const project = await store.createProject(
|
|
132
144
|
{ tenantId: DEFAULT_TENANT, name: 'bc-rb' },
|
|
133
145
|
DEFAULT_TENANT,
|
|
134
146
|
)
|
|
147
|
+
const thread = await threadStore.createThread(
|
|
148
|
+
{ projectId: project.id, title: 'bc-rb' },
|
|
149
|
+
DEFAULT_TENANT,
|
|
150
|
+
)
|
|
135
151
|
const source = await store.createSession(
|
|
136
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
152
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
137
153
|
DEFAULT_TENANT,
|
|
138
154
|
)
|
|
139
155
|
|
|
@@ -145,9 +161,9 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
145
161
|
}
|
|
146
162
|
return okExec()
|
|
147
163
|
}
|
|
148
|
-
const { deps, events } = buildDeps(store, exec)
|
|
164
|
+
const { deps, events } = buildDeps(store, threadStore, exec)
|
|
149
165
|
|
|
150
|
-
const assignments = buildAssignments(source.id, project.id, [
|
|
166
|
+
const assignments = buildAssignments(source.id, project.id, thread.id, [
|
|
151
167
|
userActor('usr_b'),
|
|
152
168
|
userActor('usr_c'),
|
|
153
169
|
userActor('usr_d'),
|
|
@@ -186,18 +202,23 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
186
202
|
|
|
187
203
|
it('source transitions to awaiting_merge + retains currentActor as coordinator (§5.4)', async () => {
|
|
188
204
|
const store = new InMemorySessionStore()
|
|
205
|
+
const threadStore = new InMemoryThreadStore()
|
|
189
206
|
const project = await store.createProject(
|
|
190
207
|
{ tenantId: DEFAULT_TENANT, name: 'coord' },
|
|
191
208
|
DEFAULT_TENANT,
|
|
192
209
|
)
|
|
210
|
+
const thread = await threadStore.createThread(
|
|
211
|
+
{ projectId: project.id, title: 'coord' },
|
|
212
|
+
DEFAULT_TENANT,
|
|
213
|
+
)
|
|
193
214
|
const coordinator = userActor('usr_source')
|
|
194
215
|
const source = await store.createSession(
|
|
195
|
-
{ projectId: project.id, currentActor: coordinator },
|
|
216
|
+
{ threadId: thread.id, projectId: project.id, currentActor: coordinator },
|
|
196
217
|
DEFAULT_TENANT,
|
|
197
218
|
)
|
|
198
219
|
|
|
199
|
-
const { deps } = buildDeps(store)
|
|
200
|
-
const assignments = buildAssignments(source.id, project.id, [
|
|
220
|
+
const { deps } = buildDeps(store, threadStore)
|
|
221
|
+
const assignments = buildAssignments(source.id, project.id, thread.id, [
|
|
201
222
|
userActor('usr_b'),
|
|
202
223
|
userActor('usr_c'),
|
|
203
224
|
])
|
|
@@ -213,12 +234,17 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
213
234
|
|
|
214
235
|
it('all recipients get isolated worktrees — zero path collisions even under N=8', async () => {
|
|
215
236
|
const store = new InMemorySessionStore()
|
|
237
|
+
const threadStore = new InMemoryThreadStore()
|
|
216
238
|
const project = await store.createProject(
|
|
217
239
|
{ tenantId: DEFAULT_TENANT, name: 'iso' },
|
|
218
240
|
DEFAULT_TENANT,
|
|
219
241
|
)
|
|
242
|
+
const thread = await threadStore.createThread(
|
|
243
|
+
{ projectId: project.id, title: 'iso' },
|
|
244
|
+
DEFAULT_TENANT,
|
|
245
|
+
)
|
|
220
246
|
const source = await store.createSession(
|
|
221
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
247
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
222
248
|
DEFAULT_TENANT,
|
|
223
249
|
)
|
|
224
250
|
|
|
@@ -233,10 +259,10 @@ describe('Integration — broadcast handoff E2E', () => {
|
|
|
233
259
|
}
|
|
234
260
|
return okExec()
|
|
235
261
|
}
|
|
236
|
-
const { deps } = buildDeps(store, exec)
|
|
262
|
+
const { deps } = buildDeps(store, threadStore, exec)
|
|
237
263
|
|
|
238
264
|
const recipients = Array.from({ length: 8 }, (_, i) => userActor(`usr_${i}`))
|
|
239
|
-
const assignments = buildAssignments(source.id, project.id, recipients)
|
|
265
|
+
const assignments = buildAssignments(source.id, project.id, thread.id, recipients)
|
|
240
266
|
|
|
241
267
|
const outcomes = await executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)
|
|
242
268
|
expect(outcomes).toHaveLength(8)
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { describe, expect, it } from 'vitest'
|
|
19
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
19
20
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
21
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
22
|
+
import type { ThreadId } from '../../../types/session/ids.js'
|
|
20
23
|
import { generateHandoffId } from '../../../utils/id.js'
|
|
21
24
|
import type { HandoffAssignment } from '../../handoff/assignment.js'
|
|
22
25
|
import { DefaultCapacityValidator } from '../../handoff/capacity.js'
|
|
@@ -31,7 +34,11 @@ import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
|
|
|
31
34
|
import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
|
|
32
35
|
import { DEFAULT_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
|
|
33
36
|
|
|
34
|
-
function buildDeps(
|
|
37
|
+
function buildDeps(
|
|
38
|
+
store: InMemorySessionStore,
|
|
39
|
+
threadStore: InMemoryThreadStore,
|
|
40
|
+
runStatus?: RunStatusResolver,
|
|
41
|
+
): SingleHandoffDeps {
|
|
35
42
|
const driver = new GitWorktreeDriver({
|
|
36
43
|
repoRoot: '/repo',
|
|
37
44
|
logger: stubLogger(),
|
|
@@ -47,31 +54,38 @@ function buildDeps(store: InMemorySessionStore, runStatus?: RunStatusResolver):
|
|
|
47
54
|
workspaceRegistry,
|
|
48
55
|
capacity: new DefaultCapacityValidator(store),
|
|
49
56
|
events,
|
|
57
|
+
threadManager: new ThreadManager({ threadStore, sessionStore: store }),
|
|
50
58
|
...(runStatus !== undefined && { runStatus }),
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
async function seedIdleSession(store: InMemorySessionStore) {
|
|
62
|
+
async function seedIdleSession(store: InMemorySessionStore, threadStore: InMemoryThreadStore) {
|
|
55
63
|
const project = await store.createProject(
|
|
56
64
|
{ tenantId: DEFAULT_TENANT, name: 'illegal' },
|
|
57
65
|
DEFAULT_TENANT,
|
|
58
66
|
)
|
|
67
|
+
const thread = await threadStore.createThread(
|
|
68
|
+
{ projectId: project.id, title: 'illegal' },
|
|
69
|
+
DEFAULT_TENANT,
|
|
70
|
+
)
|
|
59
71
|
const session = await store.createSession(
|
|
60
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
72
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
61
73
|
DEFAULT_TENANT,
|
|
62
74
|
)
|
|
63
|
-
return { project, session }
|
|
75
|
+
return { project, thread, session }
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
function buildAssignment(
|
|
67
79
|
sourceSessionId: Awaited<ReturnType<InMemorySessionStore['createSession']>>['id'],
|
|
68
80
|
projectId: Awaited<ReturnType<InMemorySessionStore['createProject']>>['id'],
|
|
81
|
+
threadId: ThreadId,
|
|
69
82
|
): HandoffAssignment {
|
|
70
83
|
return {
|
|
71
84
|
id: generateHandoffId(),
|
|
72
85
|
mode: 'single',
|
|
73
86
|
sourceSessionId,
|
|
74
87
|
tenantId: DEFAULT_TENANT,
|
|
88
|
+
threadId,
|
|
75
89
|
projectId,
|
|
76
90
|
sourceActor: userActor('usr_source'),
|
|
77
91
|
recipientActor: userActor('usr_target'),
|
|
@@ -83,13 +97,14 @@ function buildAssignment(
|
|
|
83
97
|
describe('Integration — illegal handoff transitions (§5.1)', () => {
|
|
84
98
|
it('running Run → HandoffLockRejected { reason: active_run }', async () => {
|
|
85
99
|
const store = new InMemorySessionStore()
|
|
86
|
-
const
|
|
87
|
-
const
|
|
100
|
+
const threadStore = new InMemoryThreadStore()
|
|
101
|
+
const { project, thread, session } = await seedIdleSession(store, threadStore)
|
|
102
|
+
const deps = buildDeps(store, threadStore, {
|
|
88
103
|
async blockingRun() {
|
|
89
104
|
return { reason: 'active_run' }
|
|
90
105
|
},
|
|
91
106
|
})
|
|
92
|
-
const assignment = buildAssignment(session.id, project.id)
|
|
107
|
+
const assignment = buildAssignment(session.id, project.id, thread.id)
|
|
93
108
|
|
|
94
109
|
try {
|
|
95
110
|
await executeSingleHandoff(deps, assignment, DEFAULT_TENANT)
|
|
@@ -107,15 +122,20 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
|
|
|
107
122
|
|
|
108
123
|
it('awaiting_hitl → HandoffLockRejected { reason: pending_hitl }', async () => {
|
|
109
124
|
const store = new InMemorySessionStore()
|
|
110
|
-
const
|
|
111
|
-
const
|
|
125
|
+
const threadStore = new InMemoryThreadStore()
|
|
126
|
+
const { project, thread, session } = await seedIdleSession(store, threadStore)
|
|
127
|
+
const deps = buildDeps(store, threadStore, {
|
|
112
128
|
async blockingRun() {
|
|
113
129
|
return { reason: 'pending_hitl' }
|
|
114
130
|
},
|
|
115
131
|
})
|
|
116
132
|
|
|
117
133
|
try {
|
|
118
|
-
await executeSingleHandoff(
|
|
134
|
+
await executeSingleHandoff(
|
|
135
|
+
deps,
|
|
136
|
+
buildAssignment(session.id, project.id, thread.id),
|
|
137
|
+
DEFAULT_TENANT,
|
|
138
|
+
)
|
|
119
139
|
expect.fail('expected HandoffLockRejected')
|
|
120
140
|
} catch (err) {
|
|
121
141
|
expect(err).toBeInstanceOf(HandoffLockRejected)
|
|
@@ -129,29 +149,39 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
|
|
|
129
149
|
// `pending_hitl` for both. This keeps the lock-rejection enum
|
|
130
150
|
// conservative (no new reason variant for a sub-state).
|
|
131
151
|
const store = new InMemorySessionStore()
|
|
132
|
-
const
|
|
133
|
-
const
|
|
152
|
+
const threadStore = new InMemoryThreadStore()
|
|
153
|
+
const { project, thread, session } = await seedIdleSession(store, threadStore)
|
|
154
|
+
const deps = buildDeps(store, threadStore, {
|
|
134
155
|
async blockingRun() {
|
|
135
156
|
return { reason: 'pending_hitl' }
|
|
136
157
|
},
|
|
137
158
|
})
|
|
138
159
|
|
|
139
160
|
await expect(
|
|
140
|
-
executeSingleHandoff(
|
|
161
|
+
executeSingleHandoff(
|
|
162
|
+
deps,
|
|
163
|
+
buildAssignment(session.id, project.id, thread.id),
|
|
164
|
+
DEFAULT_TENANT,
|
|
165
|
+
),
|
|
141
166
|
).rejects.toBeInstanceOf(HandoffLockRejected)
|
|
142
167
|
})
|
|
143
168
|
|
|
144
169
|
it('awaiting_subsession → HandoffLockRejected { reason: pending_subsession }', async () => {
|
|
145
170
|
const store = new InMemorySessionStore()
|
|
146
|
-
const
|
|
147
|
-
const
|
|
171
|
+
const threadStore = new InMemoryThreadStore()
|
|
172
|
+
const { project, thread, session } = await seedIdleSession(store, threadStore)
|
|
173
|
+
const deps = buildDeps(store, threadStore, {
|
|
148
174
|
async blockingRun() {
|
|
149
175
|
return { reason: 'pending_subsession' }
|
|
150
176
|
},
|
|
151
177
|
})
|
|
152
178
|
|
|
153
179
|
try {
|
|
154
|
-
await executeSingleHandoff(
|
|
180
|
+
await executeSingleHandoff(
|
|
181
|
+
deps,
|
|
182
|
+
buildAssignment(session.id, project.id, thread.id),
|
|
183
|
+
DEFAULT_TENANT,
|
|
184
|
+
)
|
|
155
185
|
expect.fail('expected HandoffLockRejected')
|
|
156
186
|
} catch (err) {
|
|
157
187
|
expect(err).toBeInstanceOf(HandoffLockRejected)
|
|
@@ -163,17 +193,22 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
|
|
|
163
193
|
// When the session itself is already non-idle (e.g. `active`), the lock
|
|
164
194
|
// rejection fires from the status check — no RunStatusResolver invoked.
|
|
165
195
|
const store = new InMemorySessionStore()
|
|
166
|
-
const
|
|
196
|
+
const threadStore = new InMemoryThreadStore()
|
|
197
|
+
const { project, thread, session } = await seedIdleSession(store, threadStore)
|
|
167
198
|
await store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
|
|
168
199
|
|
|
169
|
-
const deps = buildDeps(store, {
|
|
200
|
+
const deps = buildDeps(store, threadStore, {
|
|
170
201
|
async blockingRun() {
|
|
171
202
|
return null // resolver would allow, but status guard trips first
|
|
172
203
|
},
|
|
173
204
|
})
|
|
174
205
|
|
|
175
206
|
await expect(
|
|
176
|
-
executeSingleHandoff(
|
|
207
|
+
executeSingleHandoff(
|
|
208
|
+
deps,
|
|
209
|
+
buildAssignment(session.id, project.id, thread.id),
|
|
210
|
+
DEFAULT_TENANT,
|
|
211
|
+
),
|
|
177
212
|
).rejects.toBeInstanceOf(HandoffLockRejected)
|
|
178
213
|
})
|
|
179
214
|
})
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { describe, expect, it, vi } from 'vitest'
|
|
16
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
16
17
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
18
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
17
19
|
import type { TenantId } from '../../../types/ids/index.js'
|
|
18
20
|
import { generateHandoffId } from '../../../utils/id.js'
|
|
19
21
|
import { TenantIsolationError } from '../../errors.js'
|
|
@@ -26,7 +28,10 @@ import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
|
|
|
26
28
|
import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
|
|
27
29
|
import { DEFAULT_TENANT, OTHER_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
|
|
28
30
|
|
|
29
|
-
function buildHandoffDeps(
|
|
31
|
+
function buildHandoffDeps(
|
|
32
|
+
store: InMemorySessionStore,
|
|
33
|
+
threadStore: InMemoryThreadStore,
|
|
34
|
+
): {
|
|
30
35
|
deps: SingleHandoffDeps
|
|
31
36
|
updateCalls: Array<{ status?: string; ownerVersion?: number }>
|
|
32
37
|
} {
|
|
@@ -52,12 +57,14 @@ function buildHandoffDeps(store: InMemorySessionStore): {
|
|
|
52
57
|
return originalUpdate(session, tenantId)
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
55
61
|
return {
|
|
56
62
|
deps: {
|
|
57
63
|
store,
|
|
58
64
|
workspaceRegistry,
|
|
59
65
|
capacity: new DefaultCapacityValidator(store),
|
|
60
66
|
events: sink,
|
|
67
|
+
threadManager,
|
|
61
68
|
},
|
|
62
69
|
updateCalls,
|
|
63
70
|
}
|
|
@@ -66,23 +73,29 @@ function buildHandoffDeps(store: InMemorySessionStore): {
|
|
|
66
73
|
describe('Integration — single-recipient handoff E2E', () => {
|
|
67
74
|
it('idle → locked → commit: source previousActors grew + ownerVersion bumped atomically', async () => {
|
|
68
75
|
const store = new InMemorySessionStore()
|
|
76
|
+
const threadStore = new InMemoryThreadStore()
|
|
69
77
|
const project = await store.createProject(
|
|
70
78
|
{ tenantId: DEFAULT_TENANT, name: 'ho' },
|
|
71
79
|
DEFAULT_TENANT,
|
|
72
80
|
)
|
|
81
|
+
const thread = await threadStore.createThread(
|
|
82
|
+
{ projectId: project.id, title: 'ho' },
|
|
83
|
+
DEFAULT_TENANT,
|
|
84
|
+
)
|
|
73
85
|
const sourceActor = userActor('usr_source')
|
|
74
86
|
const recipientActor = userActor('usr_target')
|
|
75
87
|
const session = await store.createSession(
|
|
76
|
-
{ projectId: project.id, currentActor: sourceActor },
|
|
88
|
+
{ threadId: thread.id, projectId: project.id, currentActor: sourceActor },
|
|
77
89
|
DEFAULT_TENANT,
|
|
78
90
|
)
|
|
79
91
|
|
|
80
|
-
const { deps, updateCalls } = buildHandoffDeps(store)
|
|
92
|
+
const { deps, updateCalls } = buildHandoffDeps(store, threadStore)
|
|
81
93
|
const assignment: HandoffAssignment = {
|
|
82
94
|
id: generateHandoffId(),
|
|
83
95
|
mode: 'single',
|
|
84
96
|
sourceSessionId: session.id,
|
|
85
97
|
tenantId: DEFAULT_TENANT,
|
|
98
|
+
threadId: thread.id,
|
|
86
99
|
projectId: project.id,
|
|
87
100
|
sourceActor,
|
|
88
101
|
recipientActor,
|
|
@@ -115,21 +128,27 @@ describe('Integration — single-recipient handoff E2E', () => {
|
|
|
115
128
|
|
|
116
129
|
it('cross-tenant assignment rejects at entry (TenantIsolationError)', async () => {
|
|
117
130
|
const store = new InMemorySessionStore()
|
|
131
|
+
const threadStore = new InMemoryThreadStore()
|
|
118
132
|
const project = await store.createProject(
|
|
119
133
|
{ tenantId: DEFAULT_TENANT, name: 'ct' },
|
|
120
134
|
DEFAULT_TENANT,
|
|
121
135
|
)
|
|
136
|
+
const thread = await threadStore.createThread(
|
|
137
|
+
{ projectId: project.id, title: 'ct' },
|
|
138
|
+
DEFAULT_TENANT,
|
|
139
|
+
)
|
|
122
140
|
const session = await store.createSession(
|
|
123
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
141
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
124
142
|
DEFAULT_TENANT,
|
|
125
143
|
)
|
|
126
144
|
|
|
127
|
-
const { deps } = buildHandoffDeps(store)
|
|
145
|
+
const { deps } = buildHandoffDeps(store, threadStore)
|
|
128
146
|
const assignment: HandoffAssignment = {
|
|
129
147
|
id: generateHandoffId(),
|
|
130
148
|
mode: 'single',
|
|
131
149
|
sourceSessionId: session.id,
|
|
132
150
|
tenantId: OTHER_TENANT,
|
|
151
|
+
threadId: thread.id,
|
|
133
152
|
projectId: project.id,
|
|
134
153
|
sourceActor: userActor('usr_source', OTHER_TENANT),
|
|
135
154
|
recipientActor: userActor('usr_target', OTHER_TENANT),
|
|
@@ -144,21 +163,27 @@ describe('Integration — single-recipient handoff E2E', () => {
|
|
|
144
163
|
|
|
145
164
|
it('source-owned workspace provisioned for recipient', async () => {
|
|
146
165
|
const store = new InMemorySessionStore()
|
|
166
|
+
const threadStore = new InMemoryThreadStore()
|
|
147
167
|
const project = await store.createProject(
|
|
148
168
|
{ tenantId: DEFAULT_TENANT, name: 'wsp' },
|
|
149
169
|
DEFAULT_TENANT,
|
|
150
170
|
)
|
|
171
|
+
const thread = await threadStore.createThread(
|
|
172
|
+
{ projectId: project.id, title: 'wsp' },
|
|
173
|
+
DEFAULT_TENANT,
|
|
174
|
+
)
|
|
151
175
|
const source = await store.createSession(
|
|
152
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
176
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
153
177
|
DEFAULT_TENANT,
|
|
154
178
|
)
|
|
155
179
|
|
|
156
|
-
const { deps } = buildHandoffDeps(store)
|
|
180
|
+
const { deps } = buildHandoffDeps(store, threadStore)
|
|
157
181
|
const assignment: HandoffAssignment = {
|
|
158
182
|
id: generateHandoffId(),
|
|
159
183
|
mode: 'single',
|
|
160
184
|
sourceSessionId: source.id,
|
|
161
185
|
tenantId: DEFAULT_TENANT,
|
|
186
|
+
threadId: thread.id,
|
|
162
187
|
projectId: project.id,
|
|
163
188
|
sourceActor: userActor('usr_source'),
|
|
164
189
|
recipientActor: userActor('usr_target'),
|
|
@@ -190,21 +215,27 @@ describe('Integration — single-recipient handoff E2E', () => {
|
|
|
190
215
|
it('denormalized tenantId stamped on Session + SubSession records', async () => {
|
|
191
216
|
const _tenantType: TenantId = DEFAULT_TENANT
|
|
192
217
|
const store = new InMemorySessionStore()
|
|
218
|
+
const threadStore = new InMemoryThreadStore()
|
|
193
219
|
const project = await store.createProject(
|
|
194
220
|
{ tenantId: DEFAULT_TENANT, name: 'denorm' },
|
|
195
221
|
DEFAULT_TENANT,
|
|
196
222
|
)
|
|
223
|
+
const thread = await threadStore.createThread(
|
|
224
|
+
{ projectId: project.id, title: 'denorm' },
|
|
225
|
+
DEFAULT_TENANT,
|
|
226
|
+
)
|
|
197
227
|
const source = await store.createSession(
|
|
198
|
-
{ projectId: project.id, currentActor: userActor('usr_source') },
|
|
228
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
199
229
|
DEFAULT_TENANT,
|
|
200
230
|
)
|
|
201
|
-
const { deps } = buildHandoffDeps(store)
|
|
231
|
+
const { deps } = buildHandoffDeps(store, threadStore)
|
|
202
232
|
|
|
203
233
|
const assignment: HandoffAssignment = {
|
|
204
234
|
id: generateHandoffId(),
|
|
205
235
|
mode: 'single',
|
|
206
236
|
sourceSessionId: source.id,
|
|
207
237
|
tenantId: DEFAULT_TENANT,
|
|
238
|
+
threadId: thread.id,
|
|
208
239
|
projectId: project.id,
|
|
209
240
|
sourceActor: userActor('usr_source'),
|
|
210
241
|
recipientActor: userActor('usr_target'),
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, expect, it } from 'vitest'
|
|
13
|
+
import type { ThreadId } from '../../../types/session/ids.js'
|
|
13
14
|
import { TenantIsolationError } from '../../errors.js'
|
|
14
15
|
import { DEFAULT_TENANT, agentActor, buildHarness, userActor } from './_fixtures.js'
|
|
15
16
|
|
|
17
|
+
const TEST_THREAD_ID = 'thd_test' as ThreadId
|
|
18
|
+
|
|
16
19
|
describe('Integration — hierarchy lifecycle', () => {
|
|
17
20
|
it('creates Tenant → Project → Session → SubSession with properly branded IDs', async () => {
|
|
18
21
|
const { store } = buildHarness()
|
|
@@ -23,7 +26,7 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
23
26
|
expect(project.tenantId.startsWith('tnt_')).toBe(true)
|
|
24
27
|
|
|
25
28
|
const session = await store.createSession(
|
|
26
|
-
{ projectId: project.id, currentActor: userActor('usr_a') },
|
|
29
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
|
|
27
30
|
tenant,
|
|
28
31
|
)
|
|
29
32
|
expect(session.id.startsWith('ses_')).toBe(true)
|
|
@@ -34,7 +37,7 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
34
37
|
expect(session.previousActors).toEqual([])
|
|
35
38
|
|
|
36
39
|
const childSession = await store.createSession(
|
|
37
|
-
{ projectId: project.id, currentActor: agentActor('agt_worker') },
|
|
40
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_worker') },
|
|
38
41
|
tenant,
|
|
39
42
|
)
|
|
40
43
|
const subSession = await store.createSubSession(
|
|
@@ -59,15 +62,15 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
59
62
|
|
|
60
63
|
const project = await store.createProject({ tenantId: tenant, name: 'drill' }, tenant)
|
|
61
64
|
const parent = await store.createSession(
|
|
62
|
-
{ projectId: project.id, currentActor: userActor('usr_root') },
|
|
65
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_root') },
|
|
63
66
|
tenant,
|
|
64
67
|
)
|
|
65
68
|
const childA = await store.createSession(
|
|
66
|
-
{ projectId: project.id, currentActor: agentActor('agt_a') },
|
|
69
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
|
|
67
70
|
tenant,
|
|
68
71
|
)
|
|
69
72
|
const childB = await store.createSession(
|
|
70
|
-
{ projectId: project.id, currentActor: agentActor('agt_b') },
|
|
73
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_b') },
|
|
71
74
|
tenant,
|
|
72
75
|
)
|
|
73
76
|
await store.createSubSession(
|
|
@@ -120,7 +123,7 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
120
123
|
const userC = userActor('usr_c')
|
|
121
124
|
|
|
122
125
|
const session = await store.createSession(
|
|
123
|
-
{ projectId: project.id, currentActor: userA },
|
|
126
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userA },
|
|
124
127
|
tenant,
|
|
125
128
|
)
|
|
126
129
|
|
|
@@ -154,11 +157,11 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
154
157
|
|
|
155
158
|
const project = await store.createProject({ tenantId: tenant, name: 'cycle' }, tenant)
|
|
156
159
|
const sA = await store.createSession(
|
|
157
|
-
{ projectId: project.id, currentActor: userActor('usr_a') },
|
|
160
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
|
|
158
161
|
tenant,
|
|
159
162
|
)
|
|
160
163
|
const sB = await store.createSession(
|
|
161
|
-
{ projectId: project.id, currentActor: userActor('usr_b') },
|
|
164
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_b') },
|
|
162
165
|
tenant,
|
|
163
166
|
)
|
|
164
167
|
|
|
@@ -195,11 +198,11 @@ describe('Integration — hierarchy lifecycle', () => {
|
|
|
195
198
|
|
|
196
199
|
const project = await store.createProject({ tenantId: tenant, name: 'lifecycle' }, tenant)
|
|
197
200
|
const parent = await store.createSession(
|
|
198
|
-
{ projectId: project.id, currentActor: userActor('usr_a') },
|
|
201
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
|
|
199
202
|
tenant,
|
|
200
203
|
)
|
|
201
204
|
const child = await store.createSession(
|
|
202
|
-
{ projectId: project.id, currentActor: agentActor('agt_a') },
|
|
205
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
|
|
203
206
|
tenant,
|
|
204
207
|
)
|
|
205
208
|
const sub = await store.createSubSession(
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
import { describe, expect, it } from 'vitest'
|
|
15
15
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
16
16
|
import type { SessionId } from '../../../types/ids/index.js'
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
DeliverableId,
|
|
19
|
+
SubSessionId,
|
|
20
|
+
SummaryId,
|
|
21
|
+
ThreadId,
|
|
22
|
+
} from '../../../types/session/ids.js'
|
|
18
23
|
import {
|
|
19
24
|
ArtifactRefCycleError,
|
|
20
25
|
type InterventionChainLoader,
|
|
@@ -24,6 +29,8 @@ import {
|
|
|
24
29
|
import type { DeliverableRef, SessionSummaryDeliverable } from '../../summary/deliverable.js'
|
|
25
30
|
import { DEFAULT_TENANT, agentActor, userActor } from './_fixtures.js'
|
|
26
31
|
|
|
32
|
+
const TEST_THREAD_ID = 'thd_test' as ThreadId
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Build a live loader pointing at a real InMemorySessionStore. Each node
|
|
29
36
|
* resolves via `findParentSubSession`-style lookup on the store's sub-session
|
|
@@ -68,7 +75,7 @@ async function buildLinearChain(
|
|
|
68
75
|
let previous: SessionId | null = null
|
|
69
76
|
for (let i = 0; i < length; i++) {
|
|
70
77
|
const s = await store.createSession(
|
|
71
|
-
{ projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
78
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
72
79
|
DEFAULT_TENANT,
|
|
73
80
|
)
|
|
74
81
|
if (previous) {
|
|
@@ -260,15 +267,15 @@ describe('Integration — prevArtifactRef DAG against real store', () => {
|
|
|
260
267
|
DEFAULT_TENANT,
|
|
261
268
|
)
|
|
262
269
|
const sA = await store.createSession(
|
|
263
|
-
{ projectId: project.id, currentActor: agentActor('agt_a') },
|
|
270
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
|
|
264
271
|
DEFAULT_TENANT,
|
|
265
272
|
)
|
|
266
273
|
const sB = await store.createSession(
|
|
267
|
-
{ projectId: project.id, currentActor: agentActor('agt_b') },
|
|
274
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_b') },
|
|
268
275
|
DEFAULT_TENANT,
|
|
269
276
|
)
|
|
270
277
|
const sC = await store.createSession(
|
|
271
|
-
{ projectId: project.id, currentActor: agentActor('agt_c') },
|
|
278
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_c') },
|
|
272
279
|
DEFAULT_TENANT,
|
|
273
280
|
)
|
|
274
281
|
|
|
@@ -15,24 +15,26 @@ import { join } from 'node:path'
|
|
|
15
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
16
16
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
17
17
|
import { createUserMessage } from '../../../types/message/index.js'
|
|
18
|
-
import type { WorkspaceId } from '../../../types/session/ids.js'
|
|
18
|
+
import type { ThreadId, WorkspaceId } from '../../../types/session/ids.js'
|
|
19
19
|
import { ArchivalManager, ArchiveNotConfiguredError } from '../../retention/archive.js'
|
|
20
20
|
import { DiskArchiveBackend } from '../../retention/disk-backend.js'
|
|
21
21
|
import type { WorkspaceRef } from '../../workspace/ref.js'
|
|
22
22
|
import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
|
|
23
23
|
import { DEFAULT_TENANT, agentActor, userActor } from './_fixtures.js'
|
|
24
24
|
|
|
25
|
+
const TEST_THREAD_ID = 'thd_test' as ThreadId
|
|
26
|
+
|
|
25
27
|
async function seedIdleSubSession(store: InMemorySessionStore) {
|
|
26
28
|
const project = await store.createProject(
|
|
27
29
|
{ tenantId: DEFAULT_TENANT, name: 'archive' },
|
|
28
30
|
DEFAULT_TENANT,
|
|
29
31
|
)
|
|
30
32
|
const parent = await store.createSession(
|
|
31
|
-
{ projectId: project.id, currentActor: userActor('usr_a') },
|
|
33
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
|
|
32
34
|
DEFAULT_TENANT,
|
|
33
35
|
)
|
|
34
36
|
const child = await store.createSession(
|
|
35
|
-
{ projectId: project.id, currentActor: agentActor('agt_w') },
|
|
37
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_w') },
|
|
36
38
|
DEFAULT_TENANT,
|
|
37
39
|
)
|
|
38
40
|
const sub = await store.createSubSession(
|