@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
|
@@ -45,7 +45,7 @@ import type {
|
|
|
45
45
|
} from '../../session/summary/ref.js'
|
|
46
46
|
import type { MessageId, SessionId, TenantId } from '../../types/ids/index.js'
|
|
47
47
|
import type { Message } from '../../types/message/index.js'
|
|
48
|
-
import type { ProjectId, SubSessionId, SummaryId } from '../../types/session/ids.js'
|
|
48
|
+
import type { ProjectId, SubSessionId, SummaryId, ThreadId } from '../../types/session/ids.js'
|
|
49
49
|
import type {
|
|
50
50
|
CreateProjectParams,
|
|
51
51
|
CreateSessionParams,
|
|
@@ -82,6 +82,7 @@ interface PersistedProject {
|
|
|
82
82
|
|
|
83
83
|
interface PersistedSession {
|
|
84
84
|
id: SessionId
|
|
85
|
+
threadId: ThreadId
|
|
85
86
|
projectId: ProjectId
|
|
86
87
|
tenantId: TenantId
|
|
87
88
|
status: Session['status']
|
|
@@ -221,6 +222,7 @@ export class DiskSessionStore implements SessionStore {
|
|
|
221
222
|
const now = new Date()
|
|
222
223
|
const session: Session = {
|
|
223
224
|
id: generateSessionId(),
|
|
225
|
+
threadId: params.threadId,
|
|
224
226
|
projectId: params.projectId,
|
|
225
227
|
tenantId,
|
|
226
228
|
status: 'idle',
|
|
@@ -251,6 +253,58 @@ export class DiskSessionStore implements SessionStore {
|
|
|
251
253
|
return deserializeSession(raw)
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
async listSessions(threadId: ThreadId, tenantId: TenantId): Promise<readonly Session[]> {
|
|
257
|
+
// Walk projects/*/sessions/* and filter on the persisted record. Sessions
|
|
258
|
+
// don't live under a thread-scoped path in the current layout — the
|
|
259
|
+
// denormalized `threadId` on every session.json is the authority. Matches
|
|
260
|
+
// DiskThreadStore.listThreads in scan semantics.
|
|
261
|
+
//
|
|
262
|
+
// Cost: O(all sessions across all projects in the root) per call. The
|
|
263
|
+
// MVP disk store prioritizes simplicity over index freshness, matching
|
|
264
|
+
// `buildLinkageView` / `locateSession` which use the same pattern. A
|
|
265
|
+
// production driver would maintain a threadId → sessionIds secondary
|
|
266
|
+
// index populated on createSession / deleteSession. Acceptable for
|
|
267
|
+
// ThreadManager archive/delete today because those operations are
|
|
268
|
+
// admin-initiated and infrequent.
|
|
269
|
+
const projectsDir = join(this.rootDir, 'projects')
|
|
270
|
+
let projectDirs: string[]
|
|
271
|
+
try {
|
|
272
|
+
projectDirs = await readdir(projectsDir)
|
|
273
|
+
} catch (err) {
|
|
274
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
275
|
+
if (code === 'ENOENT') return []
|
|
276
|
+
throw err
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const results: Session[] = []
|
|
280
|
+
for (const rawProject of projectDirs) {
|
|
281
|
+
if (!rawProject.startsWith('prj_')) continue
|
|
282
|
+
const sessionsRoot = join(projectsDir, rawProject, 'sessions')
|
|
283
|
+
let sessionDirs: string[]
|
|
284
|
+
try {
|
|
285
|
+
sessionDirs = await readdir(sessionsRoot)
|
|
286
|
+
} catch {
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
for (const rawSessionId of sessionDirs) {
|
|
290
|
+
if (!rawSessionId.startsWith('ses_')) continue
|
|
291
|
+
const path = join(sessionsRoot, rawSessionId)
|
|
292
|
+
const raw = await readJson<PersistedSession>(join(path, 'session.json'))
|
|
293
|
+
if (!raw) continue
|
|
294
|
+
if (raw.tenantId !== tenantId) continue
|
|
295
|
+
if (raw.threadId !== threadId) continue
|
|
296
|
+
results.push(deserializeSession(raw))
|
|
297
|
+
this.sessionIndex.set(raw.id, {
|
|
298
|
+
sessionId: raw.id,
|
|
299
|
+
projectId: rawProject as ProjectId,
|
|
300
|
+
path,
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
|
|
305
|
+
return results
|
|
306
|
+
}
|
|
307
|
+
|
|
254
308
|
async updateSession(session: Session, tenantId: TenantId): Promise<void> {
|
|
255
309
|
const located = await this.locateSession(session.id)
|
|
256
310
|
if (!located) {
|
|
@@ -785,6 +839,7 @@ function deserializeProject(p: PersistedProject): Project {
|
|
|
785
839
|
function serializeSession(s: Session): PersistedSession {
|
|
786
840
|
return {
|
|
787
841
|
id: s.id,
|
|
842
|
+
threadId: s.threadId,
|
|
788
843
|
projectId: s.projectId,
|
|
789
844
|
tenantId: s.tenantId,
|
|
790
845
|
status: s.status,
|
|
@@ -800,6 +855,7 @@ function serializeSession(s: Session): PersistedSession {
|
|
|
800
855
|
function deserializeSession(s: PersistedSession): Session {
|
|
801
856
|
return {
|
|
802
857
|
id: s.id,
|
|
858
|
+
threadId: s.threadId,
|
|
803
859
|
projectId: s.projectId,
|
|
804
860
|
tenantId: s.tenantId,
|
|
805
861
|
status: s.status,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// Sub-barrel for the session-scoped persistence module (Convention #4).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// in sibling files; re-export them here so consumers import via
|
|
3
|
+
// Messages are scoped to a `SessionId` and every accessor carries explicit
|
|
4
|
+
// `TenantId` (Convention #17). Concrete implementations live in sibling
|
|
5
|
+
// files; re-export them here so consumers import via
|
|
7
6
|
// `../store/session/index.js`.
|
|
8
7
|
|
|
9
8
|
export { InMemorySessionStore } from './memory.js'
|
|
@@ -16,7 +16,7 @@ import { SessionAlreadySummarizedError } from '../../session/summary/ref.js'
|
|
|
16
16
|
import type { SessionSummaryRef } from '../../session/summary/ref.js'
|
|
17
17
|
import type { MessageId, SessionId, TenantId } from '../../types/ids/index.js'
|
|
18
18
|
import type { Message } from '../../types/message/index.js'
|
|
19
|
-
import type { ProjectId, SubSessionId } from '../../types/session/ids.js'
|
|
19
|
+
import type { ProjectId, SubSessionId, ThreadId } from '../../types/session/ids.js'
|
|
20
20
|
import type {
|
|
21
21
|
CreateProjectParams,
|
|
22
22
|
CreateSessionParams,
|
|
@@ -118,6 +118,7 @@ export class InMemorySessionStore implements SessionStore {
|
|
|
118
118
|
const now = new Date()
|
|
119
119
|
const session: Session = {
|
|
120
120
|
id: generateSessionId(),
|
|
121
|
+
threadId: params.threadId,
|
|
121
122
|
projectId: params.projectId,
|
|
122
123
|
tenantId,
|
|
123
124
|
status: 'idle',
|
|
@@ -139,6 +140,17 @@ export class InMemorySessionStore implements SessionStore {
|
|
|
139
140
|
return record.session
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
async listSessions(threadId: ThreadId, tenantId: TenantId): Promise<readonly Session[]> {
|
|
144
|
+
const matches: Session[] = []
|
|
145
|
+
for (const record of this.sessions.values()) {
|
|
146
|
+
if (record.tenantId !== tenantId) continue
|
|
147
|
+
if (record.session.threadId !== threadId) continue
|
|
148
|
+
matches.push(record.session)
|
|
149
|
+
}
|
|
150
|
+
matches.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
|
|
151
|
+
return matches
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
async updateSession(session: Session, tenantId: TenantId): Promise<void> {
|
|
143
155
|
const record = this.sessions.get(session.id)
|
|
144
156
|
if (!record) {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiskThreadStore — filesystem-backed implementation of {@link ThreadStore}.
|
|
3
|
+
*
|
|
4
|
+
* Every mutation is write-tmp-rename (Convention #8). Layout uses the Phase 2
|
|
5
|
+
* intermediate shape:
|
|
6
|
+
*
|
|
7
|
+
* {rootDir}/projects/{projectId}/threads/{threadId}/
|
|
8
|
+
* thread.json
|
|
9
|
+
*
|
|
10
|
+
* Sessions stay under `projects/{projectId}/sessions/{sessionId}/...` rather
|
|
11
|
+
* than nesting under `threads/{threadId}/` — the denormalized `threadId` on
|
|
12
|
+
* each session record (Phase 2.4 decision) makes thread-scoped queries
|
|
13
|
+
* addressable without path-level nesting, and keeps Project-scoped consumers
|
|
14
|
+
* (handoff, retention, archival) a single-directory scan. Phase 6 collapses
|
|
15
|
+
* `projects/{projectId}/` to `.namzu/` as part of `namzu init` folder binding.
|
|
16
|
+
*
|
|
17
|
+
* Tenant scoping is enforced through the JSON payload (`tenantId` field on
|
|
18
|
+
* every record), not the path — cross-tenant reads reject with
|
|
19
|
+
* {@link TenantIsolationError} (Convention #17).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from 'node:fs/promises'
|
|
23
|
+
import { join } from 'node:path'
|
|
24
|
+
import { StaleThreadError, TenantIsolationError } from '../../session/errors.js'
|
|
25
|
+
import type { Thread, ThreadStatus } from '../../session/hierarchy/thread.js'
|
|
26
|
+
import type { TenantId } from '../../types/ids/index.js'
|
|
27
|
+
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
28
|
+
import type { CreateThreadParams, ThreadStore } from '../../types/thread/store.js'
|
|
29
|
+
import { generateThreadId } from '../../utils/id.js'
|
|
30
|
+
|
|
31
|
+
/** Config for {@link DiskThreadStore}. `rootDir` is absolute. */
|
|
32
|
+
export interface DiskThreadStoreConfig {
|
|
33
|
+
rootDir: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PersistedThread {
|
|
37
|
+
id: ThreadId
|
|
38
|
+
projectId: ProjectId
|
|
39
|
+
tenantId: TenantId
|
|
40
|
+
title: string
|
|
41
|
+
status: ThreadStatus
|
|
42
|
+
ownerVersion: number
|
|
43
|
+
createdAt: string
|
|
44
|
+
updatedAt: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Index of threadId → (projectId, path). Lazy; populated on create / lookup. */
|
|
48
|
+
interface ThreadIndexEntry {
|
|
49
|
+
threadId: ThreadId
|
|
50
|
+
projectId: ProjectId
|
|
51
|
+
path: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class DiskThreadStore implements ThreadStore {
|
|
55
|
+
private readonly rootDir: string
|
|
56
|
+
private readonly threadIndex = new Map<ThreadId, ThreadIndexEntry>()
|
|
57
|
+
|
|
58
|
+
constructor(config: DiskThreadStoreConfig) {
|
|
59
|
+
this.rootDir = config.rootDir
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async createThread(params: CreateThreadParams, tenantId: TenantId): Promise<Thread> {
|
|
63
|
+
const now = new Date()
|
|
64
|
+
const thread: Thread = {
|
|
65
|
+
id: generateThreadId(),
|
|
66
|
+
projectId: params.projectId,
|
|
67
|
+
tenantId,
|
|
68
|
+
title: params.title,
|
|
69
|
+
status: 'open',
|
|
70
|
+
ownerVersion: 0,
|
|
71
|
+
createdAt: now,
|
|
72
|
+
updatedAt: now,
|
|
73
|
+
}
|
|
74
|
+
const dir = join(this.rootDir, 'projects', params.projectId, 'threads', thread.id)
|
|
75
|
+
await mkdir(dir, { recursive: true })
|
|
76
|
+
await atomicWriteJson(join(dir, 'thread.json'), serializeThread(thread))
|
|
77
|
+
this.threadIndex.set(thread.id, {
|
|
78
|
+
threadId: thread.id,
|
|
79
|
+
projectId: params.projectId,
|
|
80
|
+
path: dir,
|
|
81
|
+
})
|
|
82
|
+
return thread
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getThread(threadId: ThreadId, tenantId: TenantId): Promise<Thread | null> {
|
|
86
|
+
const located = await this.locateThread(threadId)
|
|
87
|
+
if (!located) return null
|
|
88
|
+
const raw = await readJson<PersistedThread>(join(located.path, 'thread.json'))
|
|
89
|
+
if (!raw) return null
|
|
90
|
+
this.assertTenant(raw.tenantId, tenantId, `thread(${threadId})`)
|
|
91
|
+
return deserializeThread(raw)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async updateThread(thread: Thread, tenantId: TenantId): Promise<void> {
|
|
95
|
+
if (thread.tenantId !== tenantId) {
|
|
96
|
+
throw new TenantIsolationError({
|
|
97
|
+
requested: tenantId,
|
|
98
|
+
resource: `thread(${thread.id}) payload`,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
const located = await this.locateThread(thread.id)
|
|
102
|
+
if (!located) {
|
|
103
|
+
throw new Error(`Thread ${thread.id} not found`)
|
|
104
|
+
}
|
|
105
|
+
const existing = await readJson<PersistedThread>(join(located.path, 'thread.json'))
|
|
106
|
+
if (!existing) {
|
|
107
|
+
throw new Error(`Thread ${thread.id} not found`)
|
|
108
|
+
}
|
|
109
|
+
this.assertTenant(existing.tenantId, tenantId, `thread(${thread.id})`)
|
|
110
|
+
|
|
111
|
+
// CAS on ownerVersion. On mismatch, caller must re-read + re-apply +
|
|
112
|
+
// retry. No silent overwrite (Convention #0).
|
|
113
|
+
if (thread.ownerVersion !== existing.ownerVersion) {
|
|
114
|
+
throw new StaleThreadError({
|
|
115
|
+
threadId: thread.id,
|
|
116
|
+
expectedVersion: thread.ownerVersion,
|
|
117
|
+
actualVersion: existing.ownerVersion,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const updated: Thread = {
|
|
122
|
+
...thread,
|
|
123
|
+
ownerVersion: existing.ownerVersion + 1,
|
|
124
|
+
updatedAt: new Date(),
|
|
125
|
+
}
|
|
126
|
+
await atomicWriteJson(join(located.path, 'thread.json'), serializeThread(updated))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async deleteThread(threadId: ThreadId, tenantId: TenantId): Promise<void> {
|
|
130
|
+
const located = await this.locateThread(threadId)
|
|
131
|
+
if (!located) return // Idempotent: missing = no-op.
|
|
132
|
+
const existing = await readJson<PersistedThread>(join(located.path, 'thread.json'))
|
|
133
|
+
if (!existing) return
|
|
134
|
+
this.assertTenant(existing.tenantId, tenantId, `thread(${threadId})`)
|
|
135
|
+
|
|
136
|
+
// The store does NOT enforce "no attached sessions" here — that is a
|
|
137
|
+
// cross-store precondition owned by ThreadManager. This keeps the
|
|
138
|
+
// boundary clean; ThreadStore has no awareness of SessionStore layout.
|
|
139
|
+
await rm(located.path, { recursive: true, force: true })
|
|
140
|
+
this.threadIndex.delete(threadId)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async listThreads(projectId: ProjectId, tenantId: TenantId): Promise<readonly Thread[]> {
|
|
144
|
+
const threadsDir = join(this.rootDir, 'projects', projectId, 'threads')
|
|
145
|
+
let entries: string[]
|
|
146
|
+
try {
|
|
147
|
+
entries = await readdir(threadsDir)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
150
|
+
if (code === 'ENOENT') return []
|
|
151
|
+
throw err
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const results: Thread[] = []
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.startsWith('thd_')) continue
|
|
157
|
+
const path = join(threadsDir, entry)
|
|
158
|
+
const raw = await readJson<PersistedThread>(join(path, 'thread.json'))
|
|
159
|
+
if (!raw) continue
|
|
160
|
+
// Cross-tenant records in the same directory are skipped silently —
|
|
161
|
+
// the listing is scoped to the caller's tenant. Mismatch is not an
|
|
162
|
+
// isolation violation because the caller never requested the record.
|
|
163
|
+
if (raw.tenantId !== tenantId) continue
|
|
164
|
+
results.push(deserializeThread(raw))
|
|
165
|
+
this.threadIndex.set(raw.id, { threadId: raw.id, projectId, path })
|
|
166
|
+
}
|
|
167
|
+
results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
|
|
168
|
+
return results
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async locateThread(threadId: ThreadId): Promise<ThreadIndexEntry | null> {
|
|
172
|
+
const cached = this.threadIndex.get(threadId)
|
|
173
|
+
if (cached) return cached
|
|
174
|
+
|
|
175
|
+
// Walk projects/* looking for the thread dir. Cost is bounded by number
|
|
176
|
+
// of projects (usually 1) × number of threads per project.
|
|
177
|
+
const projectsDir = join(this.rootDir, 'projects')
|
|
178
|
+
let projectDirs: string[]
|
|
179
|
+
try {
|
|
180
|
+
projectDirs = await readdir(projectsDir)
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
183
|
+
if (code === 'ENOENT') return null
|
|
184
|
+
throw err
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const rawProject of projectDirs) {
|
|
188
|
+
if (!rawProject.startsWith('prj_')) continue
|
|
189
|
+
const threadPath = join(projectsDir, rawProject, 'threads', threadId)
|
|
190
|
+
const raw = await readJson<PersistedThread>(join(threadPath, 'thread.json'))
|
|
191
|
+
if (raw?.id === threadId) {
|
|
192
|
+
const entry: ThreadIndexEntry = {
|
|
193
|
+
threadId,
|
|
194
|
+
projectId: rawProject as ProjectId,
|
|
195
|
+
path: threadPath,
|
|
196
|
+
}
|
|
197
|
+
this.threadIndex.set(threadId, entry)
|
|
198
|
+
return entry
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private assertTenant(owning: TenantId, requested: TenantId, resource: string): void {
|
|
205
|
+
if (owning !== requested) {
|
|
206
|
+
throw new TenantIsolationError({ requested, resource })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Serialization -----------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
function serializeThread(thread: Thread): PersistedThread {
|
|
214
|
+
return {
|
|
215
|
+
id: thread.id,
|
|
216
|
+
projectId: thread.projectId,
|
|
217
|
+
tenantId: thread.tenantId,
|
|
218
|
+
title: thread.title,
|
|
219
|
+
status: thread.status,
|
|
220
|
+
ownerVersion: thread.ownerVersion,
|
|
221
|
+
createdAt: thread.createdAt.toISOString(),
|
|
222
|
+
updatedAt: thread.updatedAt.toISOString(),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function deserializeThread(raw: PersistedThread): Thread {
|
|
227
|
+
return {
|
|
228
|
+
id: raw.id,
|
|
229
|
+
projectId: raw.projectId,
|
|
230
|
+
tenantId: raw.tenantId,
|
|
231
|
+
title: raw.title,
|
|
232
|
+
status: raw.status,
|
|
233
|
+
ownerVersion: raw.ownerVersion,
|
|
234
|
+
createdAt: new Date(raw.createdAt),
|
|
235
|
+
updatedAt: new Date(raw.updatedAt),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// FS helpers -----------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
async function readJson<T>(path: string): Promise<T | null> {
|
|
242
|
+
try {
|
|
243
|
+
const raw = await readFile(path, 'utf-8')
|
|
244
|
+
return JSON.parse(raw) as T
|
|
245
|
+
} catch (err) {
|
|
246
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
247
|
+
if (code === 'ENOENT') return null
|
|
248
|
+
throw err
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function atomicWriteJson(filePath: string, value: unknown): Promise<void> {
|
|
253
|
+
const tempPath = `${filePath}.tmp`
|
|
254
|
+
try {
|
|
255
|
+
await writeFile(tempPath, JSON.stringify(value, null, 2), 'utf-8')
|
|
256
|
+
await rename(tempPath, filePath)
|
|
257
|
+
} catch (err) {
|
|
258
|
+
await unlink(tempPath).catch(() => undefined)
|
|
259
|
+
throw err
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Sub-barrel for the Thread persistence module (Convention #4).
|
|
2
|
+
// Concrete implementations live in sibling files; re-export them here so
|
|
3
|
+
// consumers import via `../store/thread/index.js`.
|
|
4
|
+
|
|
5
|
+
export { InMemoryThreadStore } from './memory.js'
|
|
6
|
+
export { DiskThreadStore } from './disk.js'
|
|
7
|
+
export type { DiskThreadStoreConfig } from './disk.js'
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InMemoryThreadStore — reference in-memory implementation of
|
|
3
|
+
* {@link ThreadStore}.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the write-time CAS contract of the disk store: every
|
|
6
|
+
* `updateThread` compares the supplied `ownerVersion` against the persisted
|
|
7
|
+
* copy and rejects with `StaleThreadError` on mismatch. Convention #17:
|
|
8
|
+
* cross-tenant access throws `TenantIsolationError` with no fallback.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { StaleThreadError, TenantIsolationError } from '../../session/errors.js'
|
|
12
|
+
import type { Thread } from '../../session/hierarchy/thread.js'
|
|
13
|
+
import type { TenantId } from '../../types/ids/index.js'
|
|
14
|
+
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
15
|
+
import type { CreateThreadParams, ThreadStore } from '../../types/thread/store.js'
|
|
16
|
+
import { generateThreadId } from '../../utils/id.js'
|
|
17
|
+
|
|
18
|
+
interface ThreadRecord {
|
|
19
|
+
tenantId: TenantId
|
|
20
|
+
thread: Thread
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class InMemoryThreadStore implements ThreadStore {
|
|
24
|
+
private readonly threads = new Map<ThreadId, ThreadRecord>()
|
|
25
|
+
|
|
26
|
+
async createThread(params: CreateThreadParams, tenantId: TenantId): Promise<Thread> {
|
|
27
|
+
const now = new Date()
|
|
28
|
+
const thread: Thread = {
|
|
29
|
+
id: generateThreadId(),
|
|
30
|
+
projectId: params.projectId,
|
|
31
|
+
tenantId,
|
|
32
|
+
title: params.title,
|
|
33
|
+
status: 'open',
|
|
34
|
+
ownerVersion: 0,
|
|
35
|
+
createdAt: now,
|
|
36
|
+
updatedAt: now,
|
|
37
|
+
}
|
|
38
|
+
this.threads.set(thread.id, { tenantId, thread })
|
|
39
|
+
return thread
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getThread(threadId: ThreadId, tenantId: TenantId): Promise<Thread | null> {
|
|
43
|
+
const record = this.threads.get(threadId)
|
|
44
|
+
if (!record) return null
|
|
45
|
+
this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`)
|
|
46
|
+
return record.thread
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async updateThread(thread: Thread, tenantId: TenantId): Promise<void> {
|
|
50
|
+
if (thread.tenantId !== tenantId) {
|
|
51
|
+
throw new TenantIsolationError({
|
|
52
|
+
requested: tenantId,
|
|
53
|
+
resource: `thread(${thread.id}) payload`,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
const existing = this.threads.get(thread.id)
|
|
57
|
+
if (!existing) {
|
|
58
|
+
throw new Error(`Thread ${thread.id} not found`)
|
|
59
|
+
}
|
|
60
|
+
this.assertTenant(existing.tenantId, tenantId, `thread(${thread.id})`)
|
|
61
|
+
|
|
62
|
+
// CAS on ownerVersion — supplied version must match persisted exactly.
|
|
63
|
+
// Any drift means another writer already advanced the record; the caller
|
|
64
|
+
// must re-read + re-apply + retry.
|
|
65
|
+
if (thread.ownerVersion !== existing.thread.ownerVersion) {
|
|
66
|
+
throw new StaleThreadError({
|
|
67
|
+
threadId: thread.id,
|
|
68
|
+
expectedVersion: thread.ownerVersion,
|
|
69
|
+
actualVersion: existing.thread.ownerVersion,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const updated: Thread = {
|
|
74
|
+
...thread,
|
|
75
|
+
ownerVersion: existing.thread.ownerVersion + 1,
|
|
76
|
+
updatedAt: new Date(),
|
|
77
|
+
}
|
|
78
|
+
this.threads.set(thread.id, { tenantId, thread: updated })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async deleteThread(threadId: ThreadId, tenantId: TenantId): Promise<void> {
|
|
82
|
+
const record = this.threads.get(threadId)
|
|
83
|
+
if (!record) return // Idempotent: missing = no-op.
|
|
84
|
+
this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`)
|
|
85
|
+
this.threads.delete(threadId)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async listThreads(projectId: ProjectId, tenantId: TenantId): Promise<readonly Thread[]> {
|
|
89
|
+
const matches: Thread[] = []
|
|
90
|
+
for (const { tenantId: ownerTenant, thread } of this.threads.values()) {
|
|
91
|
+
if (ownerTenant !== tenantId) continue
|
|
92
|
+
if (thread.projectId !== projectId) continue
|
|
93
|
+
matches.push(thread)
|
|
94
|
+
}
|
|
95
|
+
matches.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
|
|
96
|
+
return matches
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private assertTenant(owning: TenantId, requested: TenantId, resource: string): void {
|
|
100
|
+
if (owning !== requested) {
|
|
101
|
+
throw new TenantIsolationError({ requested, resource })
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SDK-internal tracer/meter readers. Not re-exported by the root barrel.
|
|
2
|
+
//
|
|
3
|
+
// These wrap `@opentelemetry/api` globals so SDK internal call sites
|
|
4
|
+
// (runtime/query, runtime/query/iteration, registry/tool/execute) have a
|
|
5
|
+
// single place to resolve the active tracer/meter. When `@namzu/telemetry`
|
|
6
|
+
// is registered, its `registerTelemetry()` mutates the api globals and
|
|
7
|
+
// these readers pick up the real providers; without registration, they
|
|
8
|
+
// return the no-op defaults and every span/meter write is silently
|
|
9
|
+
// discarded — standard OTEL library behavior.
|
|
10
|
+
|
|
11
|
+
import { type Meter, type Tracer, metrics, trace } from '@opentelemetry/api'
|
|
12
|
+
|
|
13
|
+
export function getTracer(): Tracer {
|
|
14
|
+
return trace.getTracer('namzu')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getMeter(): Meter {
|
|
18
|
+
return metrics.getMeter('namzu')
|
|
19
|
+
}
|
package/src/types/agent/base.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AgentStatus, CostInfo, TokenUsage } from '../common/index.js'
|
|
2
|
-
import type { RunId, SessionId, TenantId
|
|
2
|
+
import type { RunId, SessionId, TenantId } from '../ids/index.js'
|
|
3
3
|
import type { InvocationState } from '../invocation/index.js'
|
|
4
4
|
import type { Message } from '../message/index.js'
|
|
5
5
|
import type { PermissionMode } from '../permission/index.js'
|
|
6
6
|
import type { StopReason } from '../run/stop-reason.js'
|
|
7
|
-
import type { ProjectId } from '../session/ids.js'
|
|
7
|
+
import type { ProjectId, ThreadId } from '../session/ids.js'
|
|
8
8
|
import type { TaskStore } from '../task/index.js'
|
|
9
9
|
import type { ToolAvailability } from '../tool/index.js'
|
|
10
10
|
|
|
@@ -24,29 +24,25 @@ export interface BaseAgentConfig {
|
|
|
24
24
|
env?: Record<string, string>
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Long-lived goal scope for the run. Required at runtime — agents reject
|
|
28
|
+
* configs missing this (`'X requires sessionId, projectId, and tenantId
|
|
29
|
+
* in config'`).
|
|
30
|
+
*
|
|
31
|
+
* Kept optional at the TYPE level because {@link AgentManager} stamps
|
|
32
|
+
* this field AFTER `configBuilder` returns (manager/agent/lifecycle.ts).
|
|
33
|
+
* Tightening to required is a separate task alongside
|
|
34
|
+
* `AgentFactoryOptions` carrying the triple.
|
|
29
35
|
*/
|
|
30
|
-
|
|
36
|
+
projectId?: ProjectId
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Kept optional at the TYPE level during the 0.2.x migration window
|
|
39
|
-
* because {@link AgentManager} stamps this field AFTER `configBuilder`
|
|
40
|
-
* returns (manager/agent/lifecycle.ts:228–230). That stamping path is
|
|
41
|
-
* how every `@namzu/agents` configBuilder currently gets its tenant /
|
|
42
|
-
* session / project triple; flipping the type to required without first
|
|
43
|
-
* updating every {@link AgentFactoryOptions} consumer (which does not
|
|
44
|
-
* carry these fields) would be a gratuitous downstream break.
|
|
45
|
-
*
|
|
46
|
-
* Tightening to required is Phase 9 Known Delta #6. The type-level flip
|
|
47
|
-
* lands in 0.3.0 alongside `AgentFactoryOptions` gaining the triple.
|
|
39
|
+
* Topic the run belongs to. Optional at the TYPE level for the same
|
|
40
|
+
* reason as `projectId` — {@link AgentManager} stamps this field after
|
|
41
|
+
* `configBuilder` returns so `configBuilder` implementations do not
|
|
42
|
+
* need to be updated before this tightens. Tightening to required
|
|
43
|
+
* lands with the `AgentFactoryOptions` triple refactor.
|
|
48
44
|
*/
|
|
49
|
-
|
|
45
|
+
threadId?: ThreadId
|
|
50
46
|
|
|
51
47
|
/** Session under which the run executes. See `projectId` for the tightening plan. */
|
|
52
48
|
sessionId?: SessionId
|
|
@@ -14,7 +14,14 @@ export interface AgentDefinition {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface AgentFactoryOptions {
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* API key for providers that authenticate via key (OpenAI, Anthropic,
|
|
19
|
+
* OpenRouter). Optional because BYO-provider flows (Bedrock IAM, custom
|
|
20
|
+
* `ProviderRegistry.create(...)`) resolve credentials outside this object.
|
|
21
|
+
* `configBuilder` implementations should treat an absent `apiKey` as the
|
|
22
|
+
* BYO signal and use the provider passed via the agent config instead.
|
|
23
|
+
*/
|
|
24
|
+
apiKey?: string
|
|
18
25
|
model?: string
|
|
19
26
|
workingDirectory?: string
|
|
20
27
|
tokenBudget?: number
|
|
@@ -39,8 +46,6 @@ export interface AgentFactoryOptions {
|
|
|
39
46
|
|
|
40
47
|
taskRouter?: TaskRouterConfig
|
|
41
48
|
|
|
42
|
-
threadId?: string
|
|
43
|
-
|
|
44
49
|
runId?: string
|
|
45
50
|
|
|
46
51
|
parentRunId?: string
|
package/src/types/agent/task.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { TokenUsage } from '../common/index.js'
|
|
|
4
4
|
import type { RunId, SessionId, TaskId, TenantId } from '../ids/index.js'
|
|
5
5
|
import type { Message } from '../message/index.js'
|
|
6
6
|
import type { RunEventListener } from '../run/events.js'
|
|
7
|
-
import type { ProjectId } from '../session/ids.js'
|
|
7
|
+
import type { ProjectId, ThreadId } from '../session/ids.js'
|
|
8
8
|
import type { AgentInput, BaseAgentConfig, BaseAgentResult } from './base.js'
|
|
9
9
|
import type { Agent } from './core.js'
|
|
10
10
|
import type { AgentFactoryOptions } from './factory.js'
|
|
@@ -23,15 +23,10 @@ export function isTerminalAgentTaskState(state: AgentTaskState): boolean {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Context carried into {@link AgentManager.sendMessage}.
|
|
27
|
-
* `
|
|
28
|
-
* the spawn path is the ingress point for the session hierarchy; callers
|
|
29
|
-
* provide the full scoping set
|
|
30
|
-
* matrix).
|
|
31
|
-
*
|
|
32
|
-
* `threadId` was removed — `projectId` owns the root scope. The deprecated
|
|
33
|
-
* alias `type ThreadId = ProjectId` still compiles for consumers transitioning
|
|
34
|
-
* off the name, but the shape no longer exposes a separate slot.
|
|
26
|
+
* Context carried into {@link AgentManager.sendMessage}. `tenantId`,
|
|
27
|
+
* `threadId`, `sessionId`, `projectId`, and `parentActor` are required —
|
|
28
|
+
* the spawn path is the ingress point for the session hierarchy; callers
|
|
29
|
+
* must provide the full scoping set.
|
|
35
30
|
*/
|
|
36
31
|
export interface AgentTaskContext {
|
|
37
32
|
parentRunId: RunId
|
|
@@ -49,13 +44,26 @@ export interface AgentTaskContext {
|
|
|
49
44
|
/** Isolation boundary. Required per session-hierarchy.md §12.1. */
|
|
50
45
|
tenantId: TenantId
|
|
51
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Topic the current task belongs to. Required in 0.3.0 — spawn copies
|
|
49
|
+
* this onto the child session without a second ThreadStore round-trip,
|
|
50
|
+
* and gates creation on {@link ThreadManager.requireOpen}. Children
|
|
51
|
+
* inherit the parent's `threadId` verbatim; cross-thread spawn is
|
|
52
|
+
* forbidden by design (a delegated sub-agent stays on the same topic).
|
|
53
|
+
*/
|
|
54
|
+
threadId: ThreadId
|
|
55
|
+
|
|
52
56
|
/**
|
|
53
57
|
* Parent session under which any sub-agent spawn is recorded. Required
|
|
54
58
|
* in 0.2.0; a spawn cannot be attributed without it.
|
|
55
59
|
*/
|
|
56
60
|
sessionId: SessionId
|
|
57
61
|
|
|
58
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Long-lived goal scope. Required. Denormalized from the owning Thread
|
|
64
|
+
* (see {@link Thread}) — structurally immutable per Phase 2.4 decision
|
|
65
|
+
* (sessions never cross threads, threads never cross projects).
|
|
66
|
+
*/
|
|
59
67
|
projectId: ProjectId
|
|
60
68
|
|
|
61
69
|
/**
|