@a5c-ai/krate 5.0.1-staging.00fa5317c
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/Dockerfile +31 -0
- package/README.md +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +3205 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +3125 -0
- package/dist/krate-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/krate-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1275 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +63 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +93 -0
- package/scripts/validate-ui.mjs +278 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +209 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +280 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-provider-config-controller.js +150 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +347 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +381 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +541 -0
- package/src/argocd-gitops.js +43 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +307 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +72 -0
- package/src/controller-ui.js +617 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +61 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +161 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/hooks-events.js +63 -0
- package/src/http-server.js +377 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +57 -0
- package/src/kubernetes-controller-async.js +511 -0
- package/src/kubernetes-controller.js +878 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +315 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-memory-controller.test.js +308 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +204 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +228 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +221 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +211 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/deployment.test.js +407 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- package/tests/event-bus-integration.test.js +190 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +340 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +215 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/krate.test.js +756 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +247 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
createAgentSessionTranscriptController,
|
|
5
|
+
validateAgentSessionTranscript,
|
|
6
|
+
createResource,
|
|
7
|
+
AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY
|
|
8
|
+
} from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Acceptance criteria: Slice 1.2e — Agent Session Transcript Controller
|
|
12
|
+
//
|
|
13
|
+
// An AgentSessionTranscript provides durable transcript storage linked to a
|
|
14
|
+
// session. It supports message storage with role, content, timestamp, and
|
|
15
|
+
// tool calls, along with pagination and message indexing by role or tool name.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
function makeTranscript(name, overrides = {}) {
|
|
19
|
+
return createResource('AgentSessionTranscript', { name, namespace: 'krate-org-default' }, {
|
|
20
|
+
organizationRef: 'default',
|
|
21
|
+
sessionRef: 'session-abc-123',
|
|
22
|
+
messages: [
|
|
23
|
+
{ role: 'user', content: 'Hello, agent!', timestamp: '2026-05-01T10:00:00Z' },
|
|
24
|
+
{ role: 'assistant', content: 'Hello! How can I help?', timestamp: '2026-05-01T10:00:01Z' }
|
|
25
|
+
],
|
|
26
|
+
...overrides
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// 1. Factory and shape
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
test('createAgentSessionTranscriptController returns a controller with expected methods', () => {
|
|
35
|
+
const controller = createAgentSessionTranscriptController();
|
|
36
|
+
assert.ok(controller, 'controller must be truthy');
|
|
37
|
+
assert.equal(typeof controller.validate, 'function', 'controller must expose validate');
|
|
38
|
+
assert.equal(typeof controller.getMessages, 'function', 'controller must expose getMessages');
|
|
39
|
+
assert.equal(typeof controller.getTotalMessages, 'function', 'controller must expose getTotalMessages');
|
|
40
|
+
assert.equal(typeof controller.getPage, 'function', 'controller must expose getPage');
|
|
41
|
+
assert.equal(typeof controller.getMessagesByRole, 'function', 'controller must expose getMessagesByRole');
|
|
42
|
+
assert.equal(typeof controller.getMessagesByToolName, 'function', 'controller must expose getMessagesByToolName');
|
|
43
|
+
assert.equal(controller.role, 'agent-session-transcript-controller', 'controller must declare its role');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// 2. validate — happy path
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
test('validate accepts a valid transcript with name, sessionRef, and messages', () => {
|
|
51
|
+
const controller = createAgentSessionTranscriptController();
|
|
52
|
+
const transcript = makeTranscript('session-abc-123-transcript');
|
|
53
|
+
const result = controller.validate(transcript);
|
|
54
|
+
|
|
55
|
+
assert.equal(result.valid, true, 'valid transcript must pass validation');
|
|
56
|
+
assert.ok(Array.isArray(result.errors), 'result must contain an errors array');
|
|
57
|
+
assert.equal(result.errors.length, 0, 'errors array must be empty for a valid transcript');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// 3. validate — empty messages array is acceptable
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
test('validate accepts a transcript with an empty messages array', () => {
|
|
65
|
+
const controller = createAgentSessionTranscriptController();
|
|
66
|
+
const transcript = makeTranscript('empty-transcript', { messages: [] });
|
|
67
|
+
const result = controller.validate(transcript);
|
|
68
|
+
|
|
69
|
+
assert.equal(result.valid, true, 'transcript with empty messages must pass validation');
|
|
70
|
+
assert.equal(result.errors.length, 0, 'errors array must be empty');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// 4. validate — missing name
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
test('validate rejects transcript with missing name', () => {
|
|
78
|
+
const controller = createAgentSessionTranscriptController();
|
|
79
|
+
const transcript = {
|
|
80
|
+
apiVersion: 'krate.a5c.ai/v1alpha1',
|
|
81
|
+
kind: 'AgentSessionTranscript',
|
|
82
|
+
metadata: { namespace: 'krate-org-default', labels: {}, annotations: {} },
|
|
83
|
+
spec: {
|
|
84
|
+
organizationRef: 'default',
|
|
85
|
+
sessionRef: 'session-abc-123',
|
|
86
|
+
messages: []
|
|
87
|
+
},
|
|
88
|
+
status: {}
|
|
89
|
+
};
|
|
90
|
+
const result = controller.validate(transcript);
|
|
91
|
+
|
|
92
|
+
assert.equal(result.valid, false, 'transcript without a name must fail validation');
|
|
93
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
94
|
+
assert.ok(
|
|
95
|
+
result.errors.some((e) => /name/i.test(e)),
|
|
96
|
+
'at least one error must mention "name"'
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// 5. validate — missing sessionRef
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
test('validate rejects transcript with missing sessionRef', () => {
|
|
105
|
+
const controller = createAgentSessionTranscriptController();
|
|
106
|
+
const transcript = makeTranscript('no-session-transcript');
|
|
107
|
+
delete transcript.spec.sessionRef;
|
|
108
|
+
const result = controller.validate(transcript);
|
|
109
|
+
|
|
110
|
+
assert.equal(result.valid, false, 'transcript without sessionRef must fail validation');
|
|
111
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
112
|
+
assert.ok(
|
|
113
|
+
result.errors.some((e) => /sessionRef/i.test(e)),
|
|
114
|
+
'at least one error must mention "sessionRef"'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// 6. validate — messages not an array
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
test('validate rejects transcript where messages is not an array', () => {
|
|
123
|
+
const controller = createAgentSessionTranscriptController();
|
|
124
|
+
const transcript = makeTranscript('bad-messages-transcript');
|
|
125
|
+
transcript.spec.messages = 'not-an-array';
|
|
126
|
+
const result = controller.validate(transcript);
|
|
127
|
+
|
|
128
|
+
assert.equal(result.valid, false, 'transcript with non-array messages must fail validation');
|
|
129
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
130
|
+
assert.ok(
|
|
131
|
+
result.errors.some((e) => /messages/i.test(e)),
|
|
132
|
+
'at least one error must mention "messages"'
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// 7. validate — invalid message role
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
test('validate rejects transcript with an invalid message role', () => {
|
|
141
|
+
const controller = createAgentSessionTranscriptController();
|
|
142
|
+
const transcript = makeTranscript('bad-role-transcript', {
|
|
143
|
+
messages: [{ role: 'robot', content: 'I am a robot' }]
|
|
144
|
+
});
|
|
145
|
+
const result = controller.validate(transcript);
|
|
146
|
+
|
|
147
|
+
assert.equal(result.valid, false, 'transcript with invalid role must fail validation');
|
|
148
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
149
|
+
assert.ok(
|
|
150
|
+
result.errors.some((e) => /role/i.test(e)),
|
|
151
|
+
'at least one error must mention "role"'
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// 8. validate — missing message content
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
test('validate rejects transcript where a message has no content', () => {
|
|
160
|
+
const controller = createAgentSessionTranscriptController();
|
|
161
|
+
const transcript = makeTranscript('no-content-transcript', {
|
|
162
|
+
messages: [{ role: 'user' }]
|
|
163
|
+
});
|
|
164
|
+
const result = controller.validate(transcript);
|
|
165
|
+
|
|
166
|
+
assert.equal(result.valid, false, 'message without content must fail validation');
|
|
167
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
168
|
+
assert.ok(
|
|
169
|
+
result.errors.some((e) => /content/i.test(e)),
|
|
170
|
+
'at least one error must mention "content"'
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// 9. getMessages — returns all messages in order
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
test('getMessages returns all messages in order', () => {
|
|
179
|
+
const controller = createAgentSessionTranscriptController();
|
|
180
|
+
const transcript = makeTranscript('full-transcript');
|
|
181
|
+
const messages = controller.getMessages(transcript);
|
|
182
|
+
|
|
183
|
+
assert.ok(Array.isArray(messages), 'getMessages must return an array');
|
|
184
|
+
assert.equal(messages.length, 2, 'must return all messages');
|
|
185
|
+
assert.equal(messages[0].role, 'user', 'first message must be user');
|
|
186
|
+
assert.equal(messages[1].role, 'assistant', 'second message must be assistant');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('getMessages returns empty array when no messages in spec', () => {
|
|
190
|
+
const controller = createAgentSessionTranscriptController();
|
|
191
|
+
const transcript = makeTranscript('empty-transcript', { messages: [] });
|
|
192
|
+
const messages = controller.getMessages(transcript);
|
|
193
|
+
|
|
194
|
+
assert.ok(Array.isArray(messages), 'getMessages must return an array');
|
|
195
|
+
assert.equal(messages.length, 0, 'must return empty array');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// 10. getTotalMessages — returns message count
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
test('getTotalMessages returns the total number of messages', () => {
|
|
203
|
+
const controller = createAgentSessionTranscriptController();
|
|
204
|
+
const transcript = makeTranscript('count-transcript');
|
|
205
|
+
const total = controller.getTotalMessages(transcript);
|
|
206
|
+
|
|
207
|
+
assert.equal(total, 2, 'getTotalMessages must return the correct count');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('getTotalMessages returns 0 for empty transcript', () => {
|
|
211
|
+
const controller = createAgentSessionTranscriptController();
|
|
212
|
+
const transcript = makeTranscript('zero-count-transcript', { messages: [] });
|
|
213
|
+
const total = controller.getTotalMessages(transcript);
|
|
214
|
+
|
|
215
|
+
assert.equal(total, 0, 'getTotalMessages must return 0 for empty messages');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// 11. getPage — pagination support
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
test('getPage returns first page with correct structure', () => {
|
|
223
|
+
const controller = createAgentSessionTranscriptController();
|
|
224
|
+
const messages = Array.from({ length: 25 }, (_, i) => ({
|
|
225
|
+
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
226
|
+
content: `Message ${i}`,
|
|
227
|
+
timestamp: `2026-05-01T10:00:${String(i).padStart(2, '0')}Z`
|
|
228
|
+
}));
|
|
229
|
+
const transcript = makeTranscript('paginated-transcript', { messages, pageSize: 10 });
|
|
230
|
+
const page = controller.getPage(transcript, 0);
|
|
231
|
+
|
|
232
|
+
assert.ok(page, 'getPage must return a value');
|
|
233
|
+
assert.ok(Array.isArray(page.messages), 'page must have a messages array');
|
|
234
|
+
assert.equal(page.messages.length, 10, 'first page must have 10 messages');
|
|
235
|
+
assert.equal(page.pageIndex, 0, 'pageIndex must be 0 for first page');
|
|
236
|
+
assert.equal(page.pageSize, 10, 'pageSize must be 10');
|
|
237
|
+
assert.equal(page.totalMessages, 25, 'totalMessages must be 25');
|
|
238
|
+
assert.equal(page.totalPages, 3, 'totalPages must be 3');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('getPage returns last page with remaining messages', () => {
|
|
242
|
+
const controller = createAgentSessionTranscriptController();
|
|
243
|
+
const messages = Array.from({ length: 25 }, (_, i) => ({
|
|
244
|
+
role: 'user',
|
|
245
|
+
content: `Message ${i}`
|
|
246
|
+
}));
|
|
247
|
+
const transcript = makeTranscript('paginated-transcript-2', { messages, pageSize: 10 });
|
|
248
|
+
const page = controller.getPage(transcript, 2);
|
|
249
|
+
|
|
250
|
+
assert.equal(page.messages.length, 5, 'last page must have remaining 5 messages');
|
|
251
|
+
assert.equal(page.pageIndex, 2, 'pageIndex must be 2');
|
|
252
|
+
assert.equal(page.totalPages, 3, 'totalPages must be 3');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('getPage accepts a pageSizeOverride', () => {
|
|
256
|
+
const controller = createAgentSessionTranscriptController();
|
|
257
|
+
const messages = Array.from({ length: 10 }, (_, i) => ({
|
|
258
|
+
role: 'user', content: `Message ${i}`
|
|
259
|
+
}));
|
|
260
|
+
const transcript = makeTranscript('override-pagesize-transcript', { messages, pageSize: 5 });
|
|
261
|
+
const page = controller.getPage(transcript, 0, 3);
|
|
262
|
+
|
|
263
|
+
assert.equal(page.messages.length, 3, 'pageSizeOverride must take precedence');
|
|
264
|
+
assert.equal(page.pageSize, 3, 'returned pageSize must match override');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// 12. getMessagesByRole — role indexing
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
test('getMessagesByRole returns only messages with the given role', () => {
|
|
272
|
+
const controller = createAgentSessionTranscriptController();
|
|
273
|
+
const transcript = makeTranscript('role-filter-transcript', {
|
|
274
|
+
messages: [
|
|
275
|
+
{ role: 'user', content: 'Hello' },
|
|
276
|
+
{ role: 'assistant', content: 'Hi!' },
|
|
277
|
+
{ role: 'user', content: 'Another user message' },
|
|
278
|
+
{ role: 'tool', content: 'Tool output' }
|
|
279
|
+
]
|
|
280
|
+
});
|
|
281
|
+
const userMessages = controller.getMessagesByRole(transcript, 'user');
|
|
282
|
+
|
|
283
|
+
assert.ok(Array.isArray(userMessages), 'getMessagesByRole must return an array');
|
|
284
|
+
assert.equal(userMessages.length, 2, 'must return exactly 2 user messages');
|
|
285
|
+
assert.ok(userMessages.every((m) => m.role === 'user'), 'all returned messages must have role "user"');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('getMessagesByRole returns empty array when no messages match the role', () => {
|
|
289
|
+
const controller = createAgentSessionTranscriptController();
|
|
290
|
+
const transcript = makeTranscript('no-system-transcript', {
|
|
291
|
+
messages: [
|
|
292
|
+
{ role: 'user', content: 'Hello' },
|
|
293
|
+
{ role: 'assistant', content: 'Hi!' }
|
|
294
|
+
]
|
|
295
|
+
});
|
|
296
|
+
const systemMessages = controller.getMessagesByRole(transcript, 'system');
|
|
297
|
+
|
|
298
|
+
assert.ok(Array.isArray(systemMessages), 'getMessagesByRole must return an array');
|
|
299
|
+
assert.equal(systemMessages.length, 0, 'must return empty array when no messages match');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// 13. getMessagesByToolName — tool name indexing
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
test('getMessagesByToolName returns messages containing the given tool call', () => {
|
|
307
|
+
const controller = createAgentSessionTranscriptController();
|
|
308
|
+
const transcript = makeTranscript('tool-filter-transcript', {
|
|
309
|
+
messages: [
|
|
310
|
+
{ role: 'user', content: 'Hello' },
|
|
311
|
+
{
|
|
312
|
+
role: 'assistant',
|
|
313
|
+
content: 'Using bash tool',
|
|
314
|
+
toolCalls: [{ name: 'bash', input: { command: 'ls' } }]
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
role: 'assistant',
|
|
318
|
+
content: 'Using read tool',
|
|
319
|
+
toolCalls: [{ name: 'read', input: { path: '/tmp' } }]
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
role: 'assistant',
|
|
323
|
+
content: 'Using bash again',
|
|
324
|
+
toolCalls: [{ name: 'bash', input: { command: 'pwd' } }]
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
});
|
|
328
|
+
const bashMessages = controller.getMessagesByToolName(transcript, 'bash');
|
|
329
|
+
|
|
330
|
+
assert.ok(Array.isArray(bashMessages), 'getMessagesByToolName must return an array');
|
|
331
|
+
assert.equal(bashMessages.length, 2, 'must return both messages that used bash');
|
|
332
|
+
assert.ok(
|
|
333
|
+
bashMessages.every((m) => m.toolCalls.some((tc) => tc.name === 'bash')),
|
|
334
|
+
'all returned messages must have a bash toolCall'
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('getMessagesByToolName returns empty array when no messages use the tool', () => {
|
|
339
|
+
const controller = createAgentSessionTranscriptController();
|
|
340
|
+
const transcript = makeTranscript('no-tool-transcript');
|
|
341
|
+
const result = controller.getMessagesByToolName(transcript, 'bash');
|
|
342
|
+
|
|
343
|
+
assert.ok(Array.isArray(result), 'getMessagesByToolName must return an array');
|
|
344
|
+
assert.equal(result.length, 0, 'must return empty array when no messages use the tool');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// 14. validate — rejects null resource
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
test('validate rejects null resource with a clear error', () => {
|
|
352
|
+
const controller = createAgentSessionTranscriptController();
|
|
353
|
+
const result = controller.validate(null);
|
|
354
|
+
|
|
355
|
+
assert.equal(result.valid, false, 'null resource must fail validation');
|
|
356
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
357
|
+
assert.ok(
|
|
358
|
+
result.errors.some((e) => /null|undefined/i.test(e)),
|
|
359
|
+
'error must mention null or undefined'
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
// 15. getMessages / getTotalMessages / getPage / getMessagesByRole — throw on null
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
test('getMessages throws on null resource', () => {
|
|
368
|
+
const controller = createAgentSessionTranscriptController();
|
|
369
|
+
assert.throws(
|
|
370
|
+
() => controller.getMessages(null),
|
|
371
|
+
/null|undefined/i,
|
|
372
|
+
'getMessages must throw on null resource'
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('getTotalMessages throws on null resource', () => {
|
|
377
|
+
const controller = createAgentSessionTranscriptController();
|
|
378
|
+
assert.throws(
|
|
379
|
+
() => controller.getTotalMessages(null),
|
|
380
|
+
/null|undefined/i,
|
|
381
|
+
'getTotalMessages must throw on null resource'
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('getPage throws on null resource', () => {
|
|
386
|
+
const controller = createAgentSessionTranscriptController();
|
|
387
|
+
assert.throws(
|
|
388
|
+
() => controller.getPage(null, 0),
|
|
389
|
+
/null|undefined/i,
|
|
390
|
+
'getPage must throw on null resource'
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test('getMessagesByRole throws on null resource', () => {
|
|
395
|
+
const controller = createAgentSessionTranscriptController();
|
|
396
|
+
assert.throws(
|
|
397
|
+
() => controller.getMessagesByRole(null, 'user'),
|
|
398
|
+
/null|undefined/i,
|
|
399
|
+
'getMessagesByRole must throw on null resource'
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('getMessagesByToolName throws on null resource', () => {
|
|
404
|
+
const controller = createAgentSessionTranscriptController();
|
|
405
|
+
assert.throws(
|
|
406
|
+
() => controller.getMessagesByToolName(null, 'bash'),
|
|
407
|
+
/null|undefined/i,
|
|
408
|
+
'getMessagesByToolName must throw on null resource'
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// 16. validateAgentSessionTranscript — standalone export follows existing pattern
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
test('validateAgentSessionTranscript standalone export follows existing pattern', () => {
|
|
417
|
+
assert.equal(
|
|
418
|
+
typeof validateAgentSessionTranscript,
|
|
419
|
+
'function',
|
|
420
|
+
'validateAgentSessionTranscript must be a named export'
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const transcript = makeTranscript('standalone-validate-transcript');
|
|
424
|
+
const result = validateAgentSessionTranscript(transcript);
|
|
425
|
+
|
|
426
|
+
assert.ok(result, 'validateAgentSessionTranscript must return a result');
|
|
427
|
+
assert.ok('valid' in result, 'result must have a valid property');
|
|
428
|
+
assert.ok(Array.isArray(result.errors), 'result must have an errors array');
|
|
429
|
+
assert.equal(result.valid, true, 'a fully-specified transcript must pass standalone validation');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// 17. BOUNDARY — exported with correct role
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
test('AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY is exported with correct role', () => {
|
|
437
|
+
assert.ok(AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY, 'BOUNDARY must be exported');
|
|
438
|
+
assert.equal(
|
|
439
|
+
AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY.role,
|
|
440
|
+
'agent-session-transcript-controller',
|
|
441
|
+
'BOUNDARY role must be "agent-session-transcript-controller"'
|
|
442
|
+
);
|
|
443
|
+
assert.ok(
|
|
444
|
+
Array.isArray(AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY.owns),
|
|
445
|
+
'BOUNDARY must declare owned concerns'
|
|
446
|
+
);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
// 18. getValidRoles — returns all valid roles
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
|
|
453
|
+
test('getValidRoles returns the list of valid message roles', () => {
|
|
454
|
+
const controller = createAgentSessionTranscriptController();
|
|
455
|
+
const roles = controller.getValidRoles();
|
|
456
|
+
|
|
457
|
+
assert.ok(Array.isArray(roles), 'getValidRoles must return an array');
|
|
458
|
+
assert.ok(roles.includes('user'), 'valid roles must include user');
|
|
459
|
+
assert.ok(roles.includes('assistant'), 'valid roles must include assistant');
|
|
460
|
+
assert.ok(roles.includes('tool'), 'valid roles must include tool');
|
|
461
|
+
assert.ok(roles.includes('system'), 'valid roles must include system');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
// 19. validate — invalid pageSize
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
|
|
468
|
+
test('validate rejects transcript with non-positive pageSize', () => {
|
|
469
|
+
const controller = createAgentSessionTranscriptController();
|
|
470
|
+
const transcript = makeTranscript('bad-pagesize-transcript', { pageSize: 0 });
|
|
471
|
+
const result = controller.validate(transcript);
|
|
472
|
+
|
|
473
|
+
assert.equal(result.valid, false, 'transcript with pageSize=0 must fail validation');
|
|
474
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
475
|
+
assert.ok(
|
|
476
|
+
result.errors.some((e) => /pageSize/i.test(e)),
|
|
477
|
+
'at least one error must mention "pageSize"'
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
// 20. validate — accepts all valid roles
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
test('validate accepts messages with all valid roles', () => {
|
|
486
|
+
const controller = createAgentSessionTranscriptController();
|
|
487
|
+
const transcript = makeTranscript('all-roles-transcript', {
|
|
488
|
+
messages: [
|
|
489
|
+
{ role: 'user', content: 'User message' },
|
|
490
|
+
{ role: 'assistant', content: 'Assistant message' },
|
|
491
|
+
{ role: 'tool', content: 'Tool output' },
|
|
492
|
+
{ role: 'system', content: 'System message' }
|
|
493
|
+
]
|
|
494
|
+
});
|
|
495
|
+
const result = controller.validate(transcript);
|
|
496
|
+
|
|
497
|
+
assert.equal(result.valid, true, 'transcript with all valid roles must pass validation');
|
|
498
|
+
assert.equal(result.errors.length, 0, 'errors array must be empty');
|
|
499
|
+
});
|