@a5c-ai/krate 5.0.1-staging.04a3db697
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 +3067 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +2955 -0
- package/dist/krate-summary.json +722 -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/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 +236 -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 +321 -0
- package/src/agent-workspace-controller.js +447 -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 +50 -0
- package/src/controller-ui.js +551 -0
- package/src/data-plane.js +178 -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 +95 -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 +55 -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/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -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/deployment.test.js +396 -0
- package/tests/e2e/lifecycle.test.js +117 -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 +727 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/org-scoping.test.js +687 -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/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { assembleContextBundle, createRedactionManifest } from '../src/agent-context-bundles.js';
|
|
4
|
+
import { createResource } from '../src/resource-model.js';
|
|
5
|
+
|
|
6
|
+
function makeStack(name, promptOverrides = {}, extraSpec = {}) {
|
|
7
|
+
return createResource('AgentStack', { name }, {
|
|
8
|
+
organizationRef: 'default',
|
|
9
|
+
baseAgent: 'claude-code',
|
|
10
|
+
adapter: 'babysitter',
|
|
11
|
+
runtimeIdentity: { serviceAccountRef: 'sa-agent' },
|
|
12
|
+
prompt: {
|
|
13
|
+
system: promptOverrides.system || '',
|
|
14
|
+
developer: promptOverrides.developer || '',
|
|
15
|
+
task: promptOverrides.task || '',
|
|
16
|
+
},
|
|
17
|
+
...extraSpec
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeSkill(name, promptFragment) {
|
|
22
|
+
return createResource('AgentSkill', { name }, {
|
|
23
|
+
organizationRef: 'default',
|
|
24
|
+
format: 'markdown',
|
|
25
|
+
sourceRef: `skills/${name}`,
|
|
26
|
+
promptFragment
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeContextLabel(name, promptFragment) {
|
|
31
|
+
return createResource('AgentContextLabel', { name }, {
|
|
32
|
+
organizationRef: 'default',
|
|
33
|
+
promptFragment,
|
|
34
|
+
allowedSources: ['manual']
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('agent context bundles', () => {
|
|
39
|
+
it('basic assembly with system/developer/task prompt', () => {
|
|
40
|
+
const stack = makeStack('test-stack', {
|
|
41
|
+
system: 'You are a security reviewer.',
|
|
42
|
+
developer: 'Focus on OWASP top 10.',
|
|
43
|
+
task: 'Review the authentication module.'
|
|
44
|
+
});
|
|
45
|
+
const bundle = assembleContextBundle({ stack, repository: 'my-repo', ref: 'refs/heads/main' });
|
|
46
|
+
|
|
47
|
+
assert.equal(bundle.kind, 'AgentContextBundle');
|
|
48
|
+
assert.equal(bundle.apiVersion, 'krate.a5c.ai/v1alpha1');
|
|
49
|
+
assert.ok(bundle.metadata.name.startsWith('bundle-'));
|
|
50
|
+
assert.equal(bundle.spec.organizationRef, 'default');
|
|
51
|
+
assert.equal(bundle.spec.dispatchRun, '');
|
|
52
|
+
assert.ok(typeof bundle.spec.digest === 'string' && bundle.spec.digest.length === 64);
|
|
53
|
+
|
|
54
|
+
// Prompt layers present
|
|
55
|
+
const layers = bundle.spec.promptLayers;
|
|
56
|
+
assert.equal(layers[0].role, 'system');
|
|
57
|
+
assert.equal(layers[1].role, 'developer');
|
|
58
|
+
assert.equal(layers[2].role, 'task');
|
|
59
|
+
assert.ok(layers[0].sizeBytes > 0);
|
|
60
|
+
assert.ok(layers[1].sizeBytes > 0);
|
|
61
|
+
assert.ok(layers[2].sizeBytes > 0);
|
|
62
|
+
|
|
63
|
+
// _content present (in-memory, not persisted)
|
|
64
|
+
assert.equal(bundle._content.system, 'You are a security reviewer.');
|
|
65
|
+
assert.equal(bundle._content.developer, 'Focus on OWASP top 10.');
|
|
66
|
+
assert.equal(bundle._content.task, 'Review the authentication module.');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('redaction catches API_KEY=xxx patterns', () => {
|
|
70
|
+
const stack = makeStack('test-stack', {
|
|
71
|
+
system: 'config: API_KEY=sk_live_abc123xyz',
|
|
72
|
+
developer: '',
|
|
73
|
+
task: ''
|
|
74
|
+
});
|
|
75
|
+
const bundle = assembleContextBundle({ stack });
|
|
76
|
+
assert.ok(bundle._content.system.includes('[REDACTED:secret-key]'));
|
|
77
|
+
assert.ok(!bundle._content.system.includes('sk_live_abc123xyz'));
|
|
78
|
+
assert.ok(bundle.spec.redactions.total > 0);
|
|
79
|
+
assert.ok(bundle.spec.redactions.byKind['secret-key'] > 0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('redaction catches provider tokens (sk-xxx)', () => {
|
|
83
|
+
const stack = makeStack('test-stack', {
|
|
84
|
+
system: '',
|
|
85
|
+
developer: 'Use key: sk-abcdefghijklmnopqrstuvwxyz1234567890',
|
|
86
|
+
task: ''
|
|
87
|
+
});
|
|
88
|
+
const bundle = assembleContextBundle({ stack });
|
|
89
|
+
assert.ok(bundle._content.developer.includes('[REDACTED:provider-token]'));
|
|
90
|
+
assert.ok(!bundle._content.developer.includes('sk-abcdefghijklmnopqrstuvwxyz1234567890'));
|
|
91
|
+
assert.ok(bundle.spec.redactions.byKind['provider-token'] > 0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('redaction catches Bearer tokens', () => {
|
|
95
|
+
const stack = makeStack('test-stack', {
|
|
96
|
+
system: 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.test',
|
|
97
|
+
developer: '',
|
|
98
|
+
task: ''
|
|
99
|
+
});
|
|
100
|
+
const bundle = assembleContextBundle({ stack });
|
|
101
|
+
assert.ok(bundle._content.system.includes('[REDACTED:bearer-token]'));
|
|
102
|
+
assert.ok(!bundle._content.system.includes('eyJhbGciOiJIUzI1NiJ9'));
|
|
103
|
+
assert.ok(bundle.spec.redactions.byKind['bearer-token'] > 0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('redaction catches private keys', () => {
|
|
107
|
+
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\nMIIBogIBAAJBALRpYJk...\n-----END RSA PRIVATE KEY-----';
|
|
108
|
+
const stack = makeStack('test-stack', {
|
|
109
|
+
system: `Here is a key: ${privateKey}`,
|
|
110
|
+
developer: '',
|
|
111
|
+
task: ''
|
|
112
|
+
});
|
|
113
|
+
const bundle = assembleContextBundle({ stack });
|
|
114
|
+
assert.ok(bundle._content.system.includes('[REDACTED:private-key]'));
|
|
115
|
+
assert.ok(!bundle._content.system.includes('MIIBogIBAAJBALRpYJk'));
|
|
116
|
+
assert.ok(bundle.spec.redactions.byKind['private-key'] > 0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('size truncation at 750 KiB', () => {
|
|
120
|
+
// Create a bundle that exceeds 750 KiB via many source attachments.
|
|
121
|
+
// Each source is truncated to 64 KiB per-layer, so we need enough sources
|
|
122
|
+
// to exceed 750 KiB total. 750/64 ~= 12, so 13 sources at 64 KiB each should exceed.
|
|
123
|
+
// Use content with spaces to avoid base64-credential pattern matching.
|
|
124
|
+
const chunk = 'the quick brown fox jumps.\n';
|
|
125
|
+
const bigContent = chunk.repeat(Math.ceil((64 * 1024) / chunk.length)).slice(0, 64 * 1024);
|
|
126
|
+
const stack = makeStack('test-stack', {
|
|
127
|
+
system: 'small system prompt',
|
|
128
|
+
developer: '',
|
|
129
|
+
task: ''
|
|
130
|
+
});
|
|
131
|
+
const sourceRefs = [];
|
|
132
|
+
for (let i = 0; i < 15; i++) {
|
|
133
|
+
sourceRefs.push({ kind: 'file', ref: `big-file-${i}.txt`, content: bigContent });
|
|
134
|
+
}
|
|
135
|
+
const bundle = assembleContextBundle({ stack, sourceRefs });
|
|
136
|
+
|
|
137
|
+
assert.equal(bundle.spec.limits.truncated, true);
|
|
138
|
+
assert.equal(bundle.spec.limits.maxBytes, 750 * 1024);
|
|
139
|
+
|
|
140
|
+
// Verify total content is at or near the limit
|
|
141
|
+
const allContentLen = bundle._content.system.length +
|
|
142
|
+
bundle._content.developer.length +
|
|
143
|
+
bundle._content.task.length +
|
|
144
|
+
bundle._content.sources.reduce((s, src) => s + src.content.length, 0);
|
|
145
|
+
assert.ok(allContentLen <= 750 * 1024 + 100, `total size ${allContentLen} should be near 750KiB limit`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('digest is deterministic (same input produces same digest)', () => {
|
|
149
|
+
const stack = makeStack('test-stack', {
|
|
150
|
+
system: 'Deterministic test system prompt.',
|
|
151
|
+
developer: 'Deterministic test developer prompt.',
|
|
152
|
+
task: 'Deterministic test task prompt.'
|
|
153
|
+
});
|
|
154
|
+
const sourceRefs = [{ kind: 'pr-body', ref: '#42', content: 'PR description here' }];
|
|
155
|
+
|
|
156
|
+
const bundle1 = assembleContextBundle({ stack, sourceRefs });
|
|
157
|
+
const bundle2 = assembleContextBundle({ stack, sourceRefs });
|
|
158
|
+
|
|
159
|
+
assert.equal(bundle1.spec.digest, bundle2.spec.digest);
|
|
160
|
+
assert.ok(typeof bundle1.spec.digest === 'string' && bundle1.spec.digest.length === 64);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('redaction manifest has counts only (no leaked values)', () => {
|
|
164
|
+
const stack = makeStack('test-stack', {
|
|
165
|
+
system: 'SECRET_KEY = "my-super-secret-value123" and PASSWORD=hunter2',
|
|
166
|
+
developer: '',
|
|
167
|
+
task: ''
|
|
168
|
+
});
|
|
169
|
+
const bundle = assembleContextBundle({ stack });
|
|
170
|
+
const manifest = bundle.spec.redactions;
|
|
171
|
+
|
|
172
|
+
assert.ok(typeof manifest.total === 'number' && manifest.total >= 2);
|
|
173
|
+
assert.ok(typeof manifest.byKind === 'object');
|
|
174
|
+
// Ensure the manifest only has counts, no string values that could leak secrets
|
|
175
|
+
for (const [kind, count] of Object.entries(manifest.byKind)) {
|
|
176
|
+
assert.ok(typeof kind === 'string');
|
|
177
|
+
assert.ok(typeof count === 'number');
|
|
178
|
+
}
|
|
179
|
+
// Verify no secret values in the serialized manifest
|
|
180
|
+
const serialized = JSON.stringify(manifest);
|
|
181
|
+
assert.ok(!serialized.includes('my-super-secret-value123'));
|
|
182
|
+
assert.ok(!serialized.includes('hunter2'));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('empty inputs handled gracefully', () => {
|
|
186
|
+
const stack = makeStack('test-stack', { system: '', developer: '', task: '' });
|
|
187
|
+
const bundle = assembleContextBundle({ stack });
|
|
188
|
+
|
|
189
|
+
assert.equal(bundle.kind, 'AgentContextBundle');
|
|
190
|
+
assert.ok(typeof bundle.spec.digest === 'string' && bundle.spec.digest.length === 64);
|
|
191
|
+
assert.equal(bundle.spec.promptLayers.length, 3);
|
|
192
|
+
assert.equal(bundle.spec.promptLayers[0].sizeBytes, 0);
|
|
193
|
+
assert.equal(bundle.spec.promptLayers[1].sizeBytes, 0);
|
|
194
|
+
assert.equal(bundle.spec.promptLayers[2].sizeBytes, 0);
|
|
195
|
+
assert.equal(bundle.spec.redactions.total, 0);
|
|
196
|
+
assert.equal(bundle.spec.limits.truncated, false);
|
|
197
|
+
assert.deepEqual(bundle.spec.sources, []);
|
|
198
|
+
assert.equal(bundle._content.system, '');
|
|
199
|
+
assert.equal(bundle._content.developer, '');
|
|
200
|
+
assert.equal(bundle._content.task, '');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('assembles skill and label fragments from resources', () => {
|
|
204
|
+
const stack = makeStack('test-stack', {
|
|
205
|
+
system: 'System prompt',
|
|
206
|
+
developer: '',
|
|
207
|
+
task: ''
|
|
208
|
+
}, { skillRefs: ['skill-review', 'skill-test'] });
|
|
209
|
+
const resources = {
|
|
210
|
+
AgentSkill: [
|
|
211
|
+
makeSkill('skill-review', 'Review all changed files for security issues.'),
|
|
212
|
+
makeSkill('skill-test', 'Run unit tests before proceeding.'),
|
|
213
|
+
makeSkill('skill-unused', 'This should not appear.')
|
|
214
|
+
],
|
|
215
|
+
AgentContextLabel: [
|
|
216
|
+
makeContextLabel('label-prod', 'This is a production environment.')
|
|
217
|
+
]
|
|
218
|
+
};
|
|
219
|
+
const bundle = assembleContextBundle({
|
|
220
|
+
stack,
|
|
221
|
+
contextLabels: ['label-prod'],
|
|
222
|
+
resources
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Should have system + developer + task + 2 skills + 1 label = 6 layers
|
|
226
|
+
assert.equal(bundle.spec.promptLayers.length, 6);
|
|
227
|
+
assert.equal(bundle.spec.promptLayers[3].role, 'skill:skill-review');
|
|
228
|
+
assert.equal(bundle.spec.promptLayers[4].role, 'skill:skill-test');
|
|
229
|
+
assert.equal(bundle.spec.promptLayers[5].role, 'label:label-prod');
|
|
230
|
+
assert.ok(bundle._content.skillFragments.length === 2);
|
|
231
|
+
assert.ok(bundle._content.labelFragments.length === 1);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('createRedactionManifest returns correct structure', () => {
|
|
235
|
+
const manifest = createRedactionManifest({ 'secret-key': 3, 'provider-token': 1, 'bearer-token': 2 });
|
|
236
|
+
assert.equal(manifest.total, 6);
|
|
237
|
+
assert.deepEqual(manifest.byKind, { 'secret-key': 3, 'provider-token': 1, 'bearer-token': 2 });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('createRedactionManifest with empty counts', () => {
|
|
241
|
+
const manifest = createRedactionManifest({});
|
|
242
|
+
assert.equal(manifest.total, 0);
|
|
243
|
+
assert.deepEqual(manifest.byKind, {});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('source refs are limited to MAX_ATTACHMENTS (32)', () => {
|
|
247
|
+
const stack = makeStack('test-stack', { system: '', developer: '', task: '' });
|
|
248
|
+
const sourceRefs = [];
|
|
249
|
+
for (let i = 0; i < 40; i++) {
|
|
250
|
+
sourceRefs.push({ kind: 'file', ref: `file-${i}.txt`, content: `content ${i}` });
|
|
251
|
+
}
|
|
252
|
+
const bundle = assembleContextBundle({ stack, sourceRefs });
|
|
253
|
+
assert.ok(bundle.spec.sources.length <= 32, 'should cap at 32 sources');
|
|
254
|
+
assert.ok(bundle._content.sources.length <= 32);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('prompt layers are truncated to 64 KiB each', () => {
|
|
258
|
+
const oversized = 'a'.repeat(80 * 1024); // 80 KiB
|
|
259
|
+
const stack = makeStack('test-stack', {
|
|
260
|
+
system: oversized,
|
|
261
|
+
developer: '',
|
|
262
|
+
task: ''
|
|
263
|
+
});
|
|
264
|
+
const bundle = assembleContextBundle({ stack });
|
|
265
|
+
assert.ok(bundle._content.system.length <= 64 * 1024, 'system prompt should be truncated to 64 KiB');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('redaction in source content', () => {
|
|
269
|
+
const stack = makeStack('test-stack', { system: '', developer: '', task: '' });
|
|
270
|
+
const sourceRefs = [
|
|
271
|
+
{ kind: 'pipeline-log', ref: 'run-123', content: 'Error: API_KEY=leaked_value_here failed auth' }
|
|
272
|
+
];
|
|
273
|
+
const bundle = assembleContextBundle({ stack, sourceRefs });
|
|
274
|
+
assert.ok(bundle._content.sources[0].content.includes('[REDACTED:secret-key]'));
|
|
275
|
+
assert.ok(!bundle._content.sources[0].content.includes('leaked_value_here'));
|
|
276
|
+
assert.ok(bundle.spec.redactions.total > 0);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentDispatchController, createAgentMuxClient, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function makeStack(name, spec = {}) {
|
|
6
|
+
return createResource('AgentStack', { name, namespace: 'krate-org-default' }, {
|
|
7
|
+
organizationRef: 'default',
|
|
8
|
+
baseAgent: 'claude-code',
|
|
9
|
+
adapter: 'anthropic',
|
|
10
|
+
runtimeIdentity: { serviceAccountRef: 'sa-default' },
|
|
11
|
+
...spec
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeServiceAccount(name) {
|
|
16
|
+
return createResource('AgentServiceAccount', { name, namespace: 'krate-org-default' }, {
|
|
17
|
+
organizationRef: 'default',
|
|
18
|
+
namespace: 'krate-org-default',
|
|
19
|
+
serviceAccountName: name
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeRoleBinding(name, subject) {
|
|
24
|
+
return createResource('AgentRoleBinding', { name, namespace: 'krate-org-default' }, {
|
|
25
|
+
organizationRef: 'default',
|
|
26
|
+
subject,
|
|
27
|
+
roleRef: 'agent-developer',
|
|
28
|
+
scope: 'namespace'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeSecretGrant(name, subject, purpose) {
|
|
33
|
+
return createResource('AgentSecretGrant', { name, namespace: 'krate-org-default' }, {
|
|
34
|
+
organizationRef: 'default',
|
|
35
|
+
subject,
|
|
36
|
+
secretRef: 'secret-' + purpose,
|
|
37
|
+
purpose
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildValidResources(stackName) {
|
|
42
|
+
return {
|
|
43
|
+
AgentStack: [makeStack(stackName)],
|
|
44
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
45
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
46
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test('Successful dispatch with Agent Mux available', async () => {
|
|
51
|
+
// Use a mock mux client that returns a session without making real HTTP calls
|
|
52
|
+
const muxClient = {
|
|
53
|
+
role: 'agent-mux-client',
|
|
54
|
+
isAvailable() { return true; },
|
|
55
|
+
async launchSession() { return { runId: `amux-${Date.now()}`, sessionId: `session-${Date.now()}` }; },
|
|
56
|
+
subscribeToEvents(runId, cb) { return { abort() {} }; },
|
|
57
|
+
reconcileTranscript(sessionId, events, opts) {
|
|
58
|
+
return createResource('AgentSessionTranscript', { name: `transcript-${sessionId}`, namespace: opts?.namespace || 'default' }, {
|
|
59
|
+
organizationRef: opts?.organizationRef || 'default',
|
|
60
|
+
sessionRef: sessionId,
|
|
61
|
+
messages: [],
|
|
62
|
+
cost: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
63
|
+
}, { phase: 'Reconciled', reconciledAt: new Date().toISOString() });
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const resources = buildValidResources('dispatch-stack');
|
|
67
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
68
|
+
|
|
69
|
+
const result = await controller.createManualDispatch({
|
|
70
|
+
repository: 'test-repo',
|
|
71
|
+
ref: 'main',
|
|
72
|
+
agentStack: 'dispatch-stack',
|
|
73
|
+
actor: 'test-user',
|
|
74
|
+
namespace: 'krate-org-default',
|
|
75
|
+
organizationRef: 'default',
|
|
76
|
+
resources
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
assert.equal(result.error, false, 'Dispatch should succeed');
|
|
80
|
+
assert.ok(result.run, 'Result should include run resource');
|
|
81
|
+
assert.ok(result.attempt, 'Result should include attempt resource');
|
|
82
|
+
assert.ok(result.contextBundle, 'Result should include contextBundle resource');
|
|
83
|
+
assert.ok(result.permissionSnapshot, 'Result should include permissionSnapshot');
|
|
84
|
+
assert.equal(result.run.kind, 'AgentDispatchRun');
|
|
85
|
+
assert.equal(result.attempt.kind, 'AgentDispatchAttempt');
|
|
86
|
+
assert.equal(result.run.status.phase, 'Running', 'Run phase should be Running when mux is available');
|
|
87
|
+
assert.ok(result.attempt.status.agentMuxRunId, 'Attempt should have agentMuxRunId');
|
|
88
|
+
assert.ok(result.attempt.status.agentMuxSessionId, 'Attempt should have agentMuxSessionId');
|
|
89
|
+
assert.ok(result.attempt.status.startedAt, 'Attempt should have startedAt timestamp');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('Dispatch with Agent Mux unavailable', async () => {
|
|
93
|
+
const muxClient = createAgentMuxClient({ gateway: '', enabled: false });
|
|
94
|
+
const resources = buildValidResources('dispatch-stack');
|
|
95
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
96
|
+
|
|
97
|
+
const result = await controller.createManualDispatch({
|
|
98
|
+
repository: 'test-repo',
|
|
99
|
+
ref: 'main',
|
|
100
|
+
agentStack: 'dispatch-stack',
|
|
101
|
+
actor: 'test-user',
|
|
102
|
+
namespace: 'krate-org-default',
|
|
103
|
+
organizationRef: 'default',
|
|
104
|
+
resources
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
assert.equal(result.error, false, 'Dispatch should still succeed (queued)');
|
|
108
|
+
assert.equal(result.run.status.phase, 'Queued', 'Run phase should be Queued when mux is unavailable');
|
|
109
|
+
assert.ok(result.run.status.conditions, 'Run should have conditions');
|
|
110
|
+
const muxCondition = result.run.status.conditions.find(c => c.type === 'AgentMuxBound');
|
|
111
|
+
assert.ok(muxCondition, 'Should have AgentMuxBound condition');
|
|
112
|
+
assert.equal(muxCondition.status, 'False', 'AgentMuxBound should be False');
|
|
113
|
+
assert.equal(muxCondition.reason, 'Unavailable');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Dispatch denied by permission review', async () => {
|
|
117
|
+
const resources = {
|
|
118
|
+
AgentStack: [makeStack('denied-stack', { runtimeIdentity: { serviceAccountRef: 'sa-missing' } })],
|
|
119
|
+
// No service account, no role binding, no secret grant — permission review will deny
|
|
120
|
+
};
|
|
121
|
+
const controller = createAgentDispatchController();
|
|
122
|
+
|
|
123
|
+
const result = await controller.createManualDispatch({
|
|
124
|
+
repository: 'test-repo',
|
|
125
|
+
ref: 'main',
|
|
126
|
+
agentStack: 'denied-stack',
|
|
127
|
+
actor: 'test-user',
|
|
128
|
+
namespace: 'krate-org-default',
|
|
129
|
+
organizationRef: 'default',
|
|
130
|
+
resources
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
assert.equal(result.error, true, 'Dispatch should be denied');
|
|
134
|
+
assert.equal(result.reason, 'permission-denied', 'Reason should be permission-denied');
|
|
135
|
+
assert.ok(result.review, 'Result should include the review details');
|
|
136
|
+
assert.equal(result.review.decision, 'denied');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('Stack not found', async () => {
|
|
140
|
+
const resources = buildValidResources('existing-stack');
|
|
141
|
+
const controller = createAgentDispatchController();
|
|
142
|
+
|
|
143
|
+
const result = await controller.createManualDispatch({
|
|
144
|
+
repository: 'test-repo',
|
|
145
|
+
ref: 'main',
|
|
146
|
+
agentStack: 'nonexistent-stack',
|
|
147
|
+
actor: 'test-user',
|
|
148
|
+
namespace: 'krate-org-default',
|
|
149
|
+
organizationRef: 'default',
|
|
150
|
+
resources
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
assert.equal(result.error, true, 'Dispatch should fail');
|
|
154
|
+
assert.equal(result.reason, 'stack-not-found', 'Reason should be stack-not-found');
|
|
155
|
+
assert.ok(result.message.includes('nonexistent-stack'), 'Message should name the missing stack');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('Context bundle referenced correctly', async () => {
|
|
159
|
+
const muxClient = createAgentMuxClient({ gateway: '', enabled: false });
|
|
160
|
+
const resources = buildValidResources('ref-stack');
|
|
161
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
162
|
+
|
|
163
|
+
const result = await controller.createManualDispatch({
|
|
164
|
+
repository: 'test-repo',
|
|
165
|
+
ref: 'main',
|
|
166
|
+
agentStack: 'ref-stack',
|
|
167
|
+
actor: 'test-user',
|
|
168
|
+
namespace: 'krate-org-default',
|
|
169
|
+
organizationRef: 'default',
|
|
170
|
+
resources
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
assert.equal(result.error, false, 'Dispatch should succeed');
|
|
174
|
+
assert.ok(result.contextBundle.spec.digest, 'Context bundle should have a digest');
|
|
175
|
+
assert.equal(
|
|
176
|
+
result.attempt.spec.contextBundleDigest,
|
|
177
|
+
result.contextBundle.spec.digest,
|
|
178
|
+
'Attempt contextBundleDigest should match context bundle digest'
|
|
179
|
+
);
|
|
180
|
+
assert.equal(
|
|
181
|
+
result.run.spec.contextBundleRef,
|
|
182
|
+
result.contextBundle.metadata.name,
|
|
183
|
+
'Run contextBundleRef should match context bundle name'
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function makeMemoryRepository(name) {
|
|
188
|
+
return createResource('AgentMemoryRepository', { name, namespace: 'krate-org-default' }, {
|
|
189
|
+
organizationRef: 'default',
|
|
190
|
+
repositoryRef: 'memory-repo',
|
|
191
|
+
defaultBranch: 'main',
|
|
192
|
+
layoutProfile: 'standard',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
test('Dispatch with AgentMemoryRepository creates memorySnapshot', async () => {
|
|
197
|
+
const muxClient = createAgentMuxClient({ gateway: '', enabled: false });
|
|
198
|
+
const resources = {
|
|
199
|
+
...buildValidResources('mem-stack'),
|
|
200
|
+
AgentMemoryRepository: [makeMemoryRepository('org-memory')],
|
|
201
|
+
};
|
|
202
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
203
|
+
|
|
204
|
+
const result = await controller.createManualDispatch({
|
|
205
|
+
repository: 'test-repo',
|
|
206
|
+
ref: 'main',
|
|
207
|
+
agentStack: 'mem-stack',
|
|
208
|
+
actor: 'test-user',
|
|
209
|
+
namespace: 'krate-org-default',
|
|
210
|
+
organizationRef: 'default',
|
|
211
|
+
resources,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
assert.equal(result.error, false, 'Dispatch should succeed');
|
|
215
|
+
assert.ok(result.memorySnapshot, 'Result should include memorySnapshot');
|
|
216
|
+
assert.equal(result.memorySnapshot.kind, 'AgentMemorySnapshot', 'memorySnapshot should be an AgentMemorySnapshot');
|
|
217
|
+
assert.equal(result.memorySnapshot.spec.memoryRepository, 'org-memory', 'memorySnapshot should reference the memory repo');
|
|
218
|
+
assert.equal(result.memorySnapshot.status.phase, 'Pinned', 'memorySnapshot should be Pinned');
|
|
219
|
+
assert.equal(result.run.spec.memorySnapshotRef, result.memorySnapshot.metadata.name, 'Run should reference memorySnapshot');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('Dispatch without memory repos has null memorySnapshot', async () => {
|
|
223
|
+
const muxClient = createAgentMuxClient({ gateway: '', enabled: false });
|
|
224
|
+
const resources = buildValidResources('no-mem-stack');
|
|
225
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
226
|
+
|
|
227
|
+
const result = await controller.createManualDispatch({
|
|
228
|
+
repository: 'test-repo',
|
|
229
|
+
ref: 'main',
|
|
230
|
+
agentStack: 'no-mem-stack',
|
|
231
|
+
actor: 'test-user',
|
|
232
|
+
namespace: 'krate-org-default',
|
|
233
|
+
organizationRef: 'default',
|
|
234
|
+
resources,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
assert.equal(result.error, false, 'Dispatch should succeed');
|
|
238
|
+
assert.equal(result.memorySnapshot, null, 'memorySnapshot should be null when no AgentMemoryRepository');
|
|
239
|
+
assert.equal(result.run.spec.memorySnapshotRef, undefined, 'Run should not have memorySnapshotRef');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('Dispatch with requires-approval returns early with awaitingApproval', async () => {
|
|
243
|
+
// Build resources where the secret grant has requiredApproval set,
|
|
244
|
+
// which causes the permission reviewer to return 'requires-approval'
|
|
245
|
+
const stack = makeStack('approval-stack');
|
|
246
|
+
const resources = {
|
|
247
|
+
AgentStack: [stack],
|
|
248
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
249
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
250
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')],
|
|
251
|
+
};
|
|
252
|
+
// Add requiredApproval to the secret grant so permission review returns 'requires-approval'
|
|
253
|
+
resources.AgentSecretGrant[0].spec.requiredApproval = 'manager';
|
|
254
|
+
|
|
255
|
+
const controller = createAgentDispatchController();
|
|
256
|
+
|
|
257
|
+
const result = await controller.createManualDispatch({
|
|
258
|
+
repository: 'test-repo',
|
|
259
|
+
ref: 'main',
|
|
260
|
+
agentStack: 'approval-stack',
|
|
261
|
+
actor: 'test-user',
|
|
262
|
+
namespace: 'krate-org-default',
|
|
263
|
+
organizationRef: 'default',
|
|
264
|
+
resources,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
assert.equal(result.error, false, 'Dispatch should not error — it is awaiting approval');
|
|
268
|
+
assert.equal(result.awaitingApproval, true, 'Result should indicate awaitingApproval');
|
|
269
|
+
assert.equal(result.run.status.phase, 'AwaitingApproval', 'Run phase should be AwaitingApproval');
|
|
270
|
+
assert.ok(result.approval, 'Result should include the approval resource');
|
|
271
|
+
assert.equal(result.approval.kind, 'AgentApproval', 'Approval should be an AgentApproval resource');
|
|
272
|
+
assert.equal(result.approval.status.phase, 'Pending', 'Approval should be in Pending phase');
|
|
273
|
+
assert.ok(result.permissionSnapshot, 'Result should include permissionSnapshot');
|
|
274
|
+
// No contextBundle or attempt since we returned early
|
|
275
|
+
assert.equal(result.contextBundle, undefined, 'No contextBundle when awaiting approval');
|
|
276
|
+
assert.equal(result.attempt, undefined, 'No attempt when awaiting approval');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('Successful launch creates transcript ref on run', async () => {
|
|
280
|
+
const sessionId = `session-${Date.now()}`;
|
|
281
|
+
const runId = `amux-${Date.now()}`;
|
|
282
|
+
const muxClient = {
|
|
283
|
+
role: 'agent-mux-client',
|
|
284
|
+
isAvailable() { return true; },
|
|
285
|
+
async launchSession() { return { runId, sessionId }; },
|
|
286
|
+
subscribeToEvents(rid, cb) { return { abort() {} }; },
|
|
287
|
+
reconcileTranscript(sid, events, opts) {
|
|
288
|
+
return createResource('AgentSessionTranscript', { name: `transcript-${sid}`, namespace: opts?.namespace || 'default' }, {
|
|
289
|
+
organizationRef: opts?.organizationRef || 'default',
|
|
290
|
+
sessionRef: sid,
|
|
291
|
+
messages: [],
|
|
292
|
+
cost: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
293
|
+
}, { phase: 'Reconciled', reconciledAt: new Date().toISOString() });
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
const resources = buildValidResources('transcript-stack');
|
|
297
|
+
const controller = createAgentDispatchController({ agentMuxClient: muxClient });
|
|
298
|
+
|
|
299
|
+
const result = await controller.createManualDispatch({
|
|
300
|
+
repository: 'test-repo',
|
|
301
|
+
ref: 'main',
|
|
302
|
+
agentStack: 'transcript-stack',
|
|
303
|
+
actor: 'test-user',
|
|
304
|
+
namespace: 'krate-org-default',
|
|
305
|
+
organizationRef: 'default',
|
|
306
|
+
resources,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
assert.equal(result.error, false, 'Dispatch should succeed');
|
|
310
|
+
assert.equal(result.run.status.phase, 'Running', 'Run phase should be Running');
|
|
311
|
+
assert.ok(result.transcript, 'Result should include transcript');
|
|
312
|
+
assert.equal(result.transcript.kind, 'AgentSessionTranscript', 'Transcript should be AgentSessionTranscript');
|
|
313
|
+
assert.ok(result.run.status.transcriptRef, 'Run should have transcriptRef in status');
|
|
314
|
+
assert.equal(result.run.status.transcriptRef, result.transcript.metadata.name, 'Run transcriptRef should match transcript name');
|
|
315
|
+
});
|