@namzu/sdk 0.2.0 → 0.3.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 +53 -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 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- 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/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 +1 -0
- package/dist/runtime/query/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/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 +6 -11
- 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 +0 -4
- 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/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 +15 -16
- 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/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/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/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/src/store/conversation/memory.ts +0 -144
- package/src/types/conversation/index.ts +0 -15
|
@@ -11,29 +11,27 @@ import {
|
|
|
11
11
|
import { DefaultPathBuilder, type PathBuilder } from '../../session/workspace/path-builder.js'
|
|
12
12
|
import { ActivityStore } from '../../store/activity/memory.js'
|
|
13
13
|
import { type ActivityTrackingConfig, resolveActivityTracking } from '../../types/activity/index.js'
|
|
14
|
-
import type { RunId, SessionId, TenantId
|
|
14
|
+
import type { RunId, SessionId, TenantId } from '../../types/ids/index.js'
|
|
15
15
|
import type { Message } from '../../types/message/index.js'
|
|
16
16
|
import type { PermissionMode } from '../../types/permission/index.js'
|
|
17
17
|
import type { LLMProvider } from '../../types/provider/index.js'
|
|
18
18
|
import type { AgentRunConfig } from '../../types/run/index.js'
|
|
19
|
-
import type { ProjectId } from '../../types/session/ids.js'
|
|
19
|
+
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
20
20
|
import type { ModelPricing } from '../../utils/cost.js'
|
|
21
21
|
import { generateRunId } from '../../utils/id.js'
|
|
22
22
|
import { type Logger, getRootLogger } from '../../utils/logger.js'
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Config accepted by {@link RunContextFactory.build}.
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `projectId` — consumers can still pass it, but no new path layout honors it.
|
|
25
|
+
* Config accepted by {@link RunContextFactory.build}. `sessionId`,
|
|
26
|
+
* `threadId`, `projectId`, and `tenantId` are required — runs carry the full
|
|
27
|
+
* five-layer scope (Tenant → Project → Thread → Session → Run) per
|
|
28
|
+
* Convention #17.
|
|
30
29
|
*
|
|
31
30
|
* `pathBuilder` is optional; when absent a {@link DefaultPathBuilder} is
|
|
32
|
-
* constructed against `{workingDirectory}/.namzu
|
|
33
|
-
* `.namzu/threads` path.
|
|
31
|
+
* constructed against `{workingDirectory}/.namzu`.
|
|
34
32
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* `filesystemMigrator` + `migrationSink` are optional; when absent a
|
|
34
|
+
* {@link DefaultFilesystemMigrator} wired to the
|
|
37
35
|
* {@link NOOP_FILESYSTEM_MIGRATION_SINK} is used. Migration runs once per
|
|
38
36
|
* process via {@link RunContextFactory.ensureMigrated}; the static `build`
|
|
39
37
|
* method stays synchronous so existing call sites are not broken — async
|
|
@@ -51,6 +49,7 @@ export interface RunContextConfig {
|
|
|
51
49
|
signal?: AbortSignal
|
|
52
50
|
|
|
53
51
|
sessionId: SessionId
|
|
52
|
+
threadId: ThreadId
|
|
54
53
|
projectId: ProjectId
|
|
55
54
|
tenantId: TenantId
|
|
56
55
|
|
|
@@ -73,21 +72,13 @@ export interface RunContextConfig {
|
|
|
73
72
|
depth?: number
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
/**
|
|
77
|
-
* Result of {@link RunContextFactory.build}. `threadId` remains as a
|
|
78
|
-
* deprecated read-only mirror of `projectId` for consumers still referencing
|
|
79
|
-
* the old name — scheduled for removal in 0.3.0 (session-hierarchy.md §13.1).
|
|
80
|
-
*/
|
|
75
|
+
/** Result of {@link RunContextFactory.build}. */
|
|
81
76
|
export interface RunContext {
|
|
82
77
|
runId: RunId
|
|
83
78
|
sessionId: SessionId
|
|
79
|
+
threadId: ThreadId
|
|
84
80
|
projectId: ProjectId
|
|
85
81
|
tenantId: TenantId
|
|
86
|
-
/**
|
|
87
|
-
* @deprecated Mirrors `projectId` — remove when callers migrate off the
|
|
88
|
-
* legacy name.
|
|
89
|
-
*/
|
|
90
|
-
threadId: ThreadId
|
|
91
82
|
runMgr: RunPersistence
|
|
92
83
|
activityStore: ActivityStore
|
|
93
84
|
planManager: PlanManager
|
|
@@ -156,6 +147,7 @@ export class RunContextFactory {
|
|
|
156
147
|
agent: config.agentName,
|
|
157
148
|
runId,
|
|
158
149
|
sessionId: config.sessionId,
|
|
150
|
+
threadId: config.threadId,
|
|
159
151
|
projectId: config.projectId,
|
|
160
152
|
tenantId: config.tenantId,
|
|
161
153
|
})
|
|
@@ -170,6 +162,7 @@ export class RunContextFactory {
|
|
|
170
162
|
pricing: config.pricing,
|
|
171
163
|
log,
|
|
172
164
|
sessionId: config.sessionId,
|
|
165
|
+
threadId: config.threadId,
|
|
173
166
|
tenantId: config.tenantId,
|
|
174
167
|
projectId: config.projectId,
|
|
175
168
|
parentRunId: config.parentRunId,
|
|
@@ -183,9 +176,9 @@ export class RunContextFactory {
|
|
|
183
176
|
return {
|
|
184
177
|
runId,
|
|
185
178
|
sessionId: config.sessionId,
|
|
179
|
+
threadId: config.threadId,
|
|
186
180
|
projectId: config.projectId,
|
|
187
181
|
tenantId: config.tenantId,
|
|
188
|
-
threadId: config.projectId as ThreadId,
|
|
189
182
|
runMgr,
|
|
190
183
|
activityStore,
|
|
191
184
|
planManager,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type ResumeHandler,
|
|
22
22
|
autoApproveHandler,
|
|
23
23
|
} from '../../types/hitl/index.js'
|
|
24
|
-
import type { RunId, SessionId, TenantId
|
|
24
|
+
import type { RunId, SessionId, TenantId } from '../../types/ids/index.js'
|
|
25
25
|
import type { InvocationState } from '../../types/invocation/index.js'
|
|
26
26
|
import { type Message, createSystemMessage } from '../../types/message/index.js'
|
|
27
27
|
import type { AgentPersona } from '../../types/persona/index.js'
|
|
@@ -29,7 +29,7 @@ import type { LLMProvider } from '../../types/provider/index.js'
|
|
|
29
29
|
import type { TaskRouterConfig } from '../../types/router/index.js'
|
|
30
30
|
import type { AgentRun, AgentRunConfig, RunEvent, RunEventListener } from '../../types/run/index.js'
|
|
31
31
|
import type { Sandbox, SandboxProvider } from '../../types/sandbox/index.js'
|
|
32
|
-
import type { ProjectId } from '../../types/session/ids.js'
|
|
32
|
+
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
33
33
|
import type { Skill } from '../../types/skills/index.js'
|
|
34
34
|
import type { TaskStore } from '../../types/task/index.js'
|
|
35
35
|
import type { ToolRegistryContract } from '../../types/tool/index.js'
|
|
@@ -67,30 +67,28 @@ export interface QueryParams {
|
|
|
67
67
|
resumeHandler: ResumeHandler
|
|
68
68
|
resumeFromCheckpoint?: CheckpointId
|
|
69
69
|
|
|
70
|
+
/** Session scope for the run. Required — every run is attributed to a Session. */
|
|
71
|
+
sessionId: SessionId
|
|
72
|
+
|
|
70
73
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
74
|
+
* Topic the Session lives under. Required in 0.3.0 — every run carries
|
|
75
|
+
* the full five-layer scope (Tenant → Project → Thread → Session →
|
|
76
|
+
* Run). Denormalized from `session.threadId`; callers build this
|
|
77
|
+
* alongside `sessionId` so the query pipeline never needs a second
|
|
78
|
+
* SessionStore round-trip to recover it.
|
|
73
79
|
*/
|
|
74
|
-
|
|
80
|
+
threadId: ThreadId
|
|
75
81
|
|
|
76
82
|
/** Long-lived goal scope for the run. Required. */
|
|
77
83
|
projectId: ProjectId
|
|
78
84
|
|
|
79
|
-
/** Isolation boundary. Required. */
|
|
85
|
+
/** Isolation boundary (Convention #17). Required. */
|
|
80
86
|
tenantId: TenantId
|
|
81
87
|
|
|
82
|
-
/**
|
|
83
|
-
* @deprecated Pass `projectId` instead. When both are present, `projectId`
|
|
84
|
-
* wins. During the 0.2.x migration window a caller supplying only
|
|
85
|
-
* `threadId` must also supply `projectId` — the kernel no longer infers
|
|
86
|
-
* `projectId` from a bare `threadId` on the QueryParams shape.
|
|
87
|
-
*/
|
|
88
|
-
threadId?: ThreadId
|
|
89
|
-
|
|
90
88
|
/**
|
|
91
89
|
* Optional path layout override. Defaults to a {@link DefaultPathBuilder}
|
|
92
|
-
* rooted at `{workingDirectory}/.namzu
|
|
93
|
-
*
|
|
90
|
+
* rooted at `{workingDirectory}/.namzu`. First-call filesystem migration
|
|
91
|
+
* runs on this same entry point.
|
|
94
92
|
*/
|
|
95
93
|
pathBuilder?: PathBuilder
|
|
96
94
|
|
|
@@ -158,6 +156,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Agen
|
|
|
158
156
|
messages: params.messages,
|
|
159
157
|
signal: params.signal,
|
|
160
158
|
sessionId: params.sessionId,
|
|
159
|
+
threadId: params.threadId,
|
|
161
160
|
projectId: params.projectId,
|
|
162
161
|
tenantId: params.tenantId,
|
|
163
162
|
pathBuilder: params.pathBuilder,
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
import { vi } from 'vitest'
|
|
16
16
|
import { EMPTY_TOKEN_USAGE } from '../../../constants/limits.js'
|
|
17
17
|
import { AgentManager } from '../../../manager/agent/lifecycle.js'
|
|
18
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
18
19
|
import { AgentRegistry } from '../../../registry/agent/definitions.js'
|
|
19
20
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
21
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
20
22
|
import type {
|
|
21
23
|
AgentCapabilities,
|
|
22
24
|
AgentInput,
|
|
@@ -28,7 +30,7 @@ import type { AgentDefinition } from '../../../types/agent/factory.js'
|
|
|
28
30
|
import type { AgentTaskContext, SendMessageOptions } from '../../../types/agent/task.js'
|
|
29
31
|
import type { AgentId, RunId, SessionId, TenantId, UserId } from '../../../types/ids/index.js'
|
|
30
32
|
import { createAssistantMessage } from '../../../types/message/index.js'
|
|
31
|
-
import type { ProjectId, SummaryId } from '../../../types/session/ids.js'
|
|
33
|
+
import type { ProjectId, SummaryId, ThreadId } from '../../../types/session/ids.js'
|
|
32
34
|
import { ZERO_COST } from '../../../utils/cost.js'
|
|
33
35
|
import { DefaultCapacityValidator } from '../../handoff/capacity.js'
|
|
34
36
|
import type { ActorRef } from '../../hierarchy/actor.js'
|
|
@@ -149,6 +151,8 @@ export function buildDefinition(agent: Agent<BaseAgentConfig, BaseAgentResult>):
|
|
|
149
151
|
|
|
150
152
|
export interface IntegrationHarness {
|
|
151
153
|
readonly store: InMemorySessionStore
|
|
154
|
+
readonly threadStore: InMemoryThreadStore
|
|
155
|
+
readonly threadManager: ThreadManager
|
|
152
156
|
readonly registry: AgentRegistry
|
|
153
157
|
readonly manager: AgentManager
|
|
154
158
|
readonly materializer: SessionSummaryMaterializer
|
|
@@ -178,6 +182,7 @@ export interface IntegrationHarnessOptions {
|
|
|
178
182
|
export function buildHarness(options: IntegrationHarnessOptions = {}): IntegrationHarness {
|
|
179
183
|
const tenantId = options.tenantId ?? DEFAULT_TENANT
|
|
180
184
|
const store = new InMemorySessionStore()
|
|
185
|
+
const threadStore = new InMemoryThreadStore()
|
|
181
186
|
|
|
182
187
|
const workspaceRegistry = new WorkspaceBackendRegistry()
|
|
183
188
|
if (options.withWorktreeDriver !== false) {
|
|
@@ -200,25 +205,42 @@ export function buildHarness(options: IntegrationHarnessOptions = {}): Integrati
|
|
|
200
205
|
})
|
|
201
206
|
|
|
202
207
|
const capacity = new DefaultCapacityValidator(store)
|
|
208
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
203
209
|
const registry = new AgentRegistry()
|
|
204
210
|
const manager = new AgentManager(registry, undefined, {
|
|
205
211
|
sessionStore: store,
|
|
206
212
|
summaryMaterializer: materializer,
|
|
207
213
|
workspaceRegistry,
|
|
208
214
|
capacity,
|
|
215
|
+
threadManager,
|
|
209
216
|
})
|
|
210
217
|
|
|
211
|
-
return {
|
|
218
|
+
return {
|
|
219
|
+
store,
|
|
220
|
+
threadStore,
|
|
221
|
+
threadManager,
|
|
222
|
+
registry,
|
|
223
|
+
manager,
|
|
224
|
+
materializer,
|
|
225
|
+
workspaceRegistry,
|
|
226
|
+
capacity,
|
|
227
|
+
tenantId,
|
|
228
|
+
}
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
/**
|
|
215
|
-
* Seeds a Tenant → Project → Session
|
|
216
|
-
* `active` so it is a legal spawn parent. Returns the project
|
|
217
|
-
* session for the caller to drive spawns against.
|
|
232
|
+
* Seeds a Tenant → Project → Thread → Session quadruple and flips the session
|
|
233
|
+
* into `active` so it is a legal spawn parent. Returns the project, thread,
|
|
234
|
+
* and active session for the caller to drive spawns against.
|
|
218
235
|
*/
|
|
219
236
|
export async function seedActiveParent(
|
|
220
237
|
harness: IntegrationHarness,
|
|
221
|
-
options?: {
|
|
238
|
+
options?: {
|
|
239
|
+
actor?: ActorRef
|
|
240
|
+
projectName?: string
|
|
241
|
+
tenantId?: TenantId
|
|
242
|
+
threadTitle?: string
|
|
243
|
+
},
|
|
222
244
|
) {
|
|
223
245
|
const tenantId = options?.tenantId ?? harness.tenantId
|
|
224
246
|
const actor: ActorRef = options?.actor ?? userActor('usr_root', tenantId)
|
|
@@ -226,12 +248,16 @@ export async function seedActiveParent(
|
|
|
226
248
|
{ tenantId, name: options?.projectName ?? 'integration-project' },
|
|
227
249
|
tenantId,
|
|
228
250
|
)
|
|
251
|
+
const thread = await harness.threadStore.createThread(
|
|
252
|
+
{ projectId: project.id, title: options?.threadTitle ?? 'default' },
|
|
253
|
+
tenantId,
|
|
254
|
+
)
|
|
229
255
|
const session = await harness.store.createSession(
|
|
230
|
-
{ projectId: project.id, currentActor: actor },
|
|
256
|
+
{ threadId: thread.id, projectId: project.id, currentActor: actor },
|
|
231
257
|
tenantId,
|
|
232
258
|
)
|
|
233
259
|
await harness.store.updateSession({ ...session, status: 'active' as Session['status'] }, tenantId)
|
|
234
|
-
return { project, session, actor }
|
|
260
|
+
return { project, thread, session, actor }
|
|
235
261
|
}
|
|
236
262
|
|
|
237
263
|
/**
|
|
@@ -242,6 +268,7 @@ export async function seedActiveParent(
|
|
|
242
268
|
export function buildTaskContext(params: {
|
|
243
269
|
sessionId: SessionId
|
|
244
270
|
projectId: ProjectId
|
|
271
|
+
threadId: ThreadId
|
|
245
272
|
tenantId: TenantId
|
|
246
273
|
parentActor: ActorRef
|
|
247
274
|
depth?: number
|
|
@@ -258,6 +285,7 @@ export function buildTaskContext(params: {
|
|
|
258
285
|
remaining: params.budget ?? 100_000,
|
|
259
286
|
},
|
|
260
287
|
tenantId: params.tenantId,
|
|
288
|
+
threadId: params.threadId,
|
|
261
289
|
sessionId: params.sessionId,
|
|
262
290
|
projectId: params.projectId,
|
|
263
291
|
parentActor: params.parentActor,
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration — Thread archive gate enforced at session-creation ingress
|
|
3
|
+
* sites (Phase 2.6).
|
|
4
|
+
*
|
|
5
|
+
* The Phase 2.5 commit flagged this as a known gap:
|
|
6
|
+
* > spawn and handoff paths do not yet invoke `ThreadManager.requireOpen`
|
|
7
|
+
* > before creating a child session. Until they do, the archive invariant
|
|
8
|
+
* > is best-effort.
|
|
9
|
+
*
|
|
10
|
+
* Phase 2.6 wires `requireOpen` into `AgentManager.provisionSpawn` + both
|
|
11
|
+
* handoff flows. These tests drive the archived-thread-then-attempt-creation
|
|
12
|
+
* scenarios end-to-end against the real stack.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
16
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
17
|
+
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
18
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
19
|
+
import type { AgentId, RunId, UserId } from '../../../types/ids/index.js'
|
|
20
|
+
import { generateHandoffId } from '../../../utils/id.js'
|
|
21
|
+
import { ThreadClosedError } from '../../errors.js'
|
|
22
|
+
import type { HandoffAssignment } from '../../handoff/assignment.js'
|
|
23
|
+
import { type BroadcastHandoffDeps, executeBroadcastHandoff } from '../../handoff/broadcast.js'
|
|
24
|
+
import { DefaultCapacityValidator } from '../../handoff/capacity.js'
|
|
25
|
+
import type { HandoffEventSink } from '../../handoff/events.js'
|
|
26
|
+
import { type SingleHandoffDeps, executeSingleHandoff } from '../../handoff/single.js'
|
|
27
|
+
import type { ActorRef } from '../../hierarchy/actor.js'
|
|
28
|
+
import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
|
|
29
|
+
import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_TENANT,
|
|
32
|
+
buildAgent,
|
|
33
|
+
buildDefinition,
|
|
34
|
+
buildHarness,
|
|
35
|
+
buildSendMessageOptions,
|
|
36
|
+
buildTaskContext,
|
|
37
|
+
okExec,
|
|
38
|
+
seedActiveParent,
|
|
39
|
+
stubLogger,
|
|
40
|
+
userActor,
|
|
41
|
+
} from './_fixtures.js'
|
|
42
|
+
|
|
43
|
+
describe('Integration — archive gate (Phase 2.6)', () => {
|
|
44
|
+
it('AgentManager spawn: rejects with ThreadClosedError when parent thread is archived', async () => {
|
|
45
|
+
const harness = buildHarness()
|
|
46
|
+
const { project, thread, session, actor } = await seedActiveParent(harness)
|
|
47
|
+
|
|
48
|
+
// Archive the thread via the manager so the invariant is enforced — no
|
|
49
|
+
// in-flight sessions under this thread, so archive succeeds.
|
|
50
|
+
await harness.store.updateSession({ ...session, status: 'idle' }, DEFAULT_TENANT)
|
|
51
|
+
await harness.threadManager.archive(thread.id, DEFAULT_TENANT)
|
|
52
|
+
// Flip session back to active so the spawn's capacity path reaches
|
|
53
|
+
// provisionSpawn (the archive gate should still reject).
|
|
54
|
+
await harness.store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
|
|
55
|
+
|
|
56
|
+
harness.registry.register(buildDefinition(buildAgent('worker')))
|
|
57
|
+
|
|
58
|
+
await expect(
|
|
59
|
+
harness.manager.sendMessage(
|
|
60
|
+
buildSendMessageOptions({
|
|
61
|
+
agentId: 'worker',
|
|
62
|
+
parentSessionId: session.id,
|
|
63
|
+
projectId: project.id,
|
|
64
|
+
tenantId: DEFAULT_TENANT,
|
|
65
|
+
parentActor: actor,
|
|
66
|
+
}),
|
|
67
|
+
buildTaskContext({
|
|
68
|
+
sessionId: session.id,
|
|
69
|
+
projectId: project.id,
|
|
70
|
+
threadId: thread.id,
|
|
71
|
+
tenantId: DEFAULT_TENANT,
|
|
72
|
+
parentActor: actor,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
).rejects.toBeInstanceOf(ThreadClosedError)
|
|
76
|
+
|
|
77
|
+
// Archive invariant held: no new child sessions under the archived thread.
|
|
78
|
+
const underThread = await harness.store.listSessions(thread.id, DEFAULT_TENANT)
|
|
79
|
+
expect(underThread).toHaveLength(1)
|
|
80
|
+
expect(underThread[0]?.id).toBe(session.id)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('Single handoff: rejects with ThreadClosedError when thread is archived (before CAS lock)', async () => {
|
|
84
|
+
const store = new InMemorySessionStore()
|
|
85
|
+
const threadStore = new InMemoryThreadStore()
|
|
86
|
+
const project = await store.createProject(
|
|
87
|
+
{ tenantId: DEFAULT_TENANT, name: 'archive-single' },
|
|
88
|
+
DEFAULT_TENANT,
|
|
89
|
+
)
|
|
90
|
+
const thread = await threadStore.createThread(
|
|
91
|
+
{ projectId: project.id, title: 'archive-single' },
|
|
92
|
+
DEFAULT_TENANT,
|
|
93
|
+
)
|
|
94
|
+
const sourceActor: ActorRef = {
|
|
95
|
+
kind: 'user',
|
|
96
|
+
userId: 'usr_source' as UserId,
|
|
97
|
+
tenantId: DEFAULT_TENANT,
|
|
98
|
+
}
|
|
99
|
+
const session = await store.createSession(
|
|
100
|
+
{ threadId: thread.id, projectId: project.id, currentActor: sourceActor },
|
|
101
|
+
DEFAULT_TENANT,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// Archive the thread directly — session is idle, so archive precondition
|
|
105
|
+
// holds via listSessions.
|
|
106
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
107
|
+
await threadManager.archive(thread.id, DEFAULT_TENANT)
|
|
108
|
+
|
|
109
|
+
const driver = new GitWorktreeDriver({
|
|
110
|
+
repoRoot: '/repo',
|
|
111
|
+
logger: stubLogger(),
|
|
112
|
+
execFile: async () => okExec(),
|
|
113
|
+
})
|
|
114
|
+
const workspaceRegistry = new WorkspaceBackendRegistry()
|
|
115
|
+
workspaceRegistry.register(driver)
|
|
116
|
+
|
|
117
|
+
const events: HandoffEventSink = {
|
|
118
|
+
onLocked: vi.fn(),
|
|
119
|
+
onUnlocked: vi.fn(),
|
|
120
|
+
onCommitted: vi.fn(),
|
|
121
|
+
onBroadcastRollback: vi.fn(),
|
|
122
|
+
}
|
|
123
|
+
const deps: SingleHandoffDeps = {
|
|
124
|
+
store,
|
|
125
|
+
workspaceRegistry,
|
|
126
|
+
capacity: new DefaultCapacityValidator(store),
|
|
127
|
+
events,
|
|
128
|
+
threadManager,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const assignment: HandoffAssignment = {
|
|
132
|
+
id: generateHandoffId(),
|
|
133
|
+
mode: 'single',
|
|
134
|
+
sourceSessionId: session.id,
|
|
135
|
+
tenantId: DEFAULT_TENANT,
|
|
136
|
+
threadId: thread.id,
|
|
137
|
+
projectId: project.id,
|
|
138
|
+
sourceActor,
|
|
139
|
+
recipientActor: userActor('usr_target'),
|
|
140
|
+
expectedOwnerVersion: 0,
|
|
141
|
+
createdAt: new Date('2026-04-19'),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await expect(executeSingleHandoff(deps, assignment, DEFAULT_TENANT)).rejects.toBeInstanceOf(
|
|
145
|
+
ThreadClosedError,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// Critical: source session must still be idle (lock never acquired).
|
|
149
|
+
const reloaded = await store.getSession(session.id, DEFAULT_TENANT)
|
|
150
|
+
expect(reloaded?.status).toBe('idle')
|
|
151
|
+
expect(reloaded?.ownerVersion).toBe(0)
|
|
152
|
+
expect(events.onLocked).not.toHaveBeenCalled()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('Broadcast handoff: rejects with ThreadClosedError when thread is archived (no CAS, no worktrees)', async () => {
|
|
156
|
+
const store = new InMemorySessionStore()
|
|
157
|
+
const threadStore = new InMemoryThreadStore()
|
|
158
|
+
const project = await store.createProject(
|
|
159
|
+
{ tenantId: DEFAULT_TENANT, name: 'archive-bc' },
|
|
160
|
+
DEFAULT_TENANT,
|
|
161
|
+
)
|
|
162
|
+
const thread = await threadStore.createThread(
|
|
163
|
+
{ projectId: project.id, title: 'archive-bc' },
|
|
164
|
+
DEFAULT_TENANT,
|
|
165
|
+
)
|
|
166
|
+
const source = await store.createSession(
|
|
167
|
+
{ threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
|
|
168
|
+
DEFAULT_TENANT,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
172
|
+
await threadManager.archive(thread.id, DEFAULT_TENANT)
|
|
173
|
+
|
|
174
|
+
let worktreeAdds = 0
|
|
175
|
+
const driver = new GitWorktreeDriver({
|
|
176
|
+
repoRoot: '/repo',
|
|
177
|
+
logger: stubLogger(),
|
|
178
|
+
execFile: async (_file, args) => {
|
|
179
|
+
if (args.includes('add')) worktreeAdds += 1
|
|
180
|
+
return okExec()
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
const workspaceRegistry = new WorkspaceBackendRegistry()
|
|
184
|
+
workspaceRegistry.register(driver)
|
|
185
|
+
|
|
186
|
+
const events: HandoffEventSink = {
|
|
187
|
+
onLocked: vi.fn(),
|
|
188
|
+
onUnlocked: vi.fn(),
|
|
189
|
+
onCommitted: vi.fn(),
|
|
190
|
+
onBroadcastRollback: vi.fn(),
|
|
191
|
+
}
|
|
192
|
+
const deps: BroadcastHandoffDeps = {
|
|
193
|
+
store,
|
|
194
|
+
workspaceRegistry,
|
|
195
|
+
capacity: new DefaultCapacityValidator(store),
|
|
196
|
+
events,
|
|
197
|
+
threadManager,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const assignments: HandoffAssignment[] = [userActor('usr_b'), userActor('usr_c')].map(
|
|
201
|
+
(recipientActor) => ({
|
|
202
|
+
id: generateHandoffId(),
|
|
203
|
+
mode: 'broadcast' as const,
|
|
204
|
+
sourceSessionId: source.id,
|
|
205
|
+
tenantId: DEFAULT_TENANT,
|
|
206
|
+
threadId: thread.id,
|
|
207
|
+
projectId: project.id,
|
|
208
|
+
sourceActor: userActor('usr_source'),
|
|
209
|
+
recipientActor,
|
|
210
|
+
expectedOwnerVersion: 0,
|
|
211
|
+
broadcastId: 'bc_archive',
|
|
212
|
+
createdAt: new Date('2026-04-19'),
|
|
213
|
+
}),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
await expect(executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)).rejects.toBeInstanceOf(
|
|
217
|
+
ThreadClosedError,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
// Source never locked, no worktrees provisioned, no rollback event —
|
|
221
|
+
// the archive gate short-circuited before any side-effect landed.
|
|
222
|
+
const reloaded = await store.getSession(source.id, DEFAULT_TENANT)
|
|
223
|
+
expect(reloaded?.status).toBe('idle')
|
|
224
|
+
expect(reloaded?.ownerVersion).toBe(0)
|
|
225
|
+
expect(events.onLocked).not.toHaveBeenCalled()
|
|
226
|
+
expect(events.onBroadcastRollback).not.toHaveBeenCalled()
|
|
227
|
+
expect(worktreeAdds).toBe(0)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('Phase 2.6 closure: post-archive spawn via AgentManager rejects (kernel ingress path gated)', async () => {
|
|
231
|
+
// End-to-end proof that the production ingress path honors the
|
|
232
|
+
// archive status. Direct `SessionStore.createSession` / `updateSession`
|
|
233
|
+
// remain ungated at the store layer (by design — the store has no
|
|
234
|
+
// cross-store awareness), so this test exercises the manager path,
|
|
235
|
+
// not the store boundary.
|
|
236
|
+
const harness = buildHarness()
|
|
237
|
+
const { project, thread, session } = await seedActiveParent(harness)
|
|
238
|
+
|
|
239
|
+
await harness.store.updateSession({ ...session, status: 'idle' }, DEFAULT_TENANT)
|
|
240
|
+
await harness.threadManager.archive(thread.id, DEFAULT_TENANT)
|
|
241
|
+
|
|
242
|
+
// Attempt to spawn directly via context threading (bypass the fixture
|
|
243
|
+
// builder to prove the gate trips from AgentManager, not from the
|
|
244
|
+
// fixture).
|
|
245
|
+
harness.registry.register(buildDefinition(buildAgent('leaker')))
|
|
246
|
+
|
|
247
|
+
const childActor: ActorRef = {
|
|
248
|
+
kind: 'agent',
|
|
249
|
+
agentId: 'leaker' as AgentId,
|
|
250
|
+
tenantId: DEFAULT_TENANT,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Flip session to active again so capacity checks pass and the
|
|
254
|
+
// archive-gate is what fails — not an earlier guard.
|
|
255
|
+
await harness.store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
|
|
256
|
+
|
|
257
|
+
await expect(
|
|
258
|
+
harness.manager.sendMessage(
|
|
259
|
+
{
|
|
260
|
+
agentId: 'leaker',
|
|
261
|
+
input: { messages: [], workingDirectory: '/tmp' },
|
|
262
|
+
parentSessionId: session.id,
|
|
263
|
+
tenantId: DEFAULT_TENANT,
|
|
264
|
+
projectId: project.id,
|
|
265
|
+
parentActor: childActor,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
parentRunId: 'run_post_archive' as RunId,
|
|
269
|
+
parentAgentId: 'supervisor',
|
|
270
|
+
parentAbortController: new AbortController(),
|
|
271
|
+
depth: 0,
|
|
272
|
+
budgetTracker: { total: 10_000, remaining: 10_000 },
|
|
273
|
+
tenantId: DEFAULT_TENANT,
|
|
274
|
+
threadId: thread.id,
|
|
275
|
+
sessionId: session.id,
|
|
276
|
+
projectId: project.id,
|
|
277
|
+
parentActor: childActor,
|
|
278
|
+
},
|
|
279
|
+
),
|
|
280
|
+
).rejects.toBeInstanceOf(ThreadClosedError)
|
|
281
|
+
|
|
282
|
+
// The gate tripped before any observable side effect — listSessions
|
|
283
|
+
// still shows only the original seeded session, not a smuggled child.
|
|
284
|
+
const underThread = await harness.store.listSessions(thread.id, DEFAULT_TENANT)
|
|
285
|
+
expect(underThread).toHaveLength(1)
|
|
286
|
+
expect(underThread[0]?.id).toBe(session.id)
|
|
287
|
+
})
|
|
288
|
+
})
|
|
@@ -28,14 +28,18 @@ import {
|
|
|
28
28
|
describe('Integration — capacity caps at spawn sites', () => {
|
|
29
29
|
it('spawn at depth 4 accepted; depth 5 rejected (default maxDelegationDepth=4)', async () => {
|
|
30
30
|
const harness = buildHarness()
|
|
31
|
-
const { project } = await seedActiveParent(harness)
|
|
31
|
+
const { project, thread } = await seedActiveParent(harness)
|
|
32
32
|
|
|
33
33
|
// Build a depth-4 ancestry chain under the project. Each layer flips to
|
|
34
34
|
// `active` so it is a legal spawn parent via the real AgentManager.
|
|
35
35
|
const chainActors: ActorRef[] = [userActor('usr_root')]
|
|
36
36
|
let parentSessionId = (
|
|
37
37
|
await harness.store.createSession(
|
|
38
|
-
{
|
|
38
|
+
{
|
|
39
|
+
threadId: thread.id,
|
|
40
|
+
projectId: project.id,
|
|
41
|
+
currentActor: chainActors[0] ?? userActor('usr_root'),
|
|
42
|
+
},
|
|
39
43
|
DEFAULT_TENANT,
|
|
40
44
|
)
|
|
41
45
|
).id
|
|
@@ -44,7 +48,7 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
44
48
|
|
|
45
49
|
for (let i = 0; i < 4; i++) {
|
|
46
50
|
const child = await harness.store.createSession(
|
|
47
|
-
{ projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
51
|
+
{ threadId: thread.id, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
48
52
|
DEFAULT_TENANT,
|
|
49
53
|
)
|
|
50
54
|
await harness.store.createSubSession(
|
|
@@ -68,6 +72,7 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
68
72
|
const context = buildTaskContext({
|
|
69
73
|
sessionId: tail,
|
|
70
74
|
projectId: project.id,
|
|
75
|
+
threadId: thread.id,
|
|
71
76
|
tenantId: DEFAULT_TENANT,
|
|
72
77
|
parentActor: userActor('usr_root'),
|
|
73
78
|
})
|
|
@@ -86,12 +91,12 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
86
91
|
|
|
87
92
|
it('spawn at width 8 accepted; 9th sibling rejected (default maxDelegationWidth=8)', async () => {
|
|
88
93
|
const harness = buildHarness()
|
|
89
|
-
const { project, session, actor } = await seedActiveParent(harness)
|
|
94
|
+
const { project, thread, session, actor } = await seedActiveParent(harness)
|
|
90
95
|
|
|
91
96
|
// Seed 8 existing children directly through the store.
|
|
92
97
|
for (let i = 0; i < 8; i++) {
|
|
93
98
|
const child = await harness.store.createSession(
|
|
94
|
-
{ projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
99
|
+
{ threadId: thread.id, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
|
|
95
100
|
DEFAULT_TENANT,
|
|
96
101
|
)
|
|
97
102
|
await harness.store.createSubSession(
|
|
@@ -110,6 +115,7 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
110
115
|
const context = buildTaskContext({
|
|
111
116
|
sessionId: session.id,
|
|
112
117
|
projectId: project.id,
|
|
118
|
+
threadId: thread.id,
|
|
113
119
|
tenantId: DEFAULT_TENANT,
|
|
114
120
|
parentActor: actor,
|
|
115
121
|
})
|
|
@@ -133,7 +139,7 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
133
139
|
// DefaultCapacityValidator(store) so it walks the actual persisted
|
|
134
140
|
// parent chain — confirms the wiring rather than a direct unit call.
|
|
135
141
|
const harness = buildHarness()
|
|
136
|
-
const { project, session, actor } = await seedActiveParent(harness)
|
|
142
|
+
const { project, thread, session, actor } = await seedActiveParent(harness)
|
|
137
143
|
|
|
138
144
|
harness.registry.register(buildDefinition(buildAgent('worker')))
|
|
139
145
|
|
|
@@ -142,6 +148,7 @@ describe('Integration — capacity caps at spawn sites', () => {
|
|
|
142
148
|
const context = buildTaskContext({
|
|
143
149
|
sessionId: session.id,
|
|
144
150
|
projectId: project.id,
|
|
151
|
+
threadId: thread.id,
|
|
145
152
|
tenantId: DEFAULT_TENANT,
|
|
146
153
|
parentActor: actor,
|
|
147
154
|
})
|