@a5c-ai/kradle 5.0.1-staging.3abdf9534c25
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 +187 -0
- package/bin/kradle-demo.mjs +23 -0
- package/bin/kradle-server.mjs +14 -0
- package/dist/kradle-controller-ui.json +3482 -0
- package/dist/kradle-lifecycle.json +201 -0
- package/dist/kradle-runtime-snapshot.json +3125 -0
- package/dist/kradle-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-kradle-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/architecture-v2.md +2759 -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/crd-behaviors-and-relationships.md +3926 -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/integration-and-design-decisions.md +1530 -0
- package/docs/kradle-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 +1291 -0
- package/docs/product-requirements.md +62 -0
- package/docs/requirements-v2.md +235 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/sdk-api-reference.md +1108 -0
- package/docs/system-requirements.md +90 -0
- package/docs/system-spec-v2.md +1230 -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/docs/web-console-spec.md +533 -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 +66 -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 +95 -0
- package/scripts/validate-ui.mjs +305 -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 +549 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-identity-migration.js +115 -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 +589 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-persona-controller.js +135 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-prompt-composition.js +55 -0
- package/src/agent-provider-config-controller.js +151 -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 +421 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +387 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +621 -0
- package/src/argocd-gitops.js +43 -0
- package/src/artifact-registry-controller.js +542 -0
- package/src/assistant-runtime.js +284 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +310 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +112 -0
- package/src/controller-ui.js +620 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +397 -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 +221 -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/health-probes.js +134 -0
- package/src/hooks-events.js +63 -0
- package/src/hooks-lifecycle.js +117 -0
- package/src/http-server.js +409 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +71 -0
- package/src/jitsi-agent-bridge.js +141 -0
- package/src/jitsi-meeting-controller.js +291 -0
- package/src/jitsi-sync-controller.js +198 -0
- package/src/kradle-inference-service-controller.js +246 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +904 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/model-route-controller.js +364 -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 +282 -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/virtual-model-controller.js +538 -0
- package/src/virtual-model-hook-bridge.js +200 -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 +679 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-identity-migration.test.js +87 -0
- package/tests/agent-memory-controller.test.js +461 -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 +389 -0
- package/tests/agent-mux-integration.test.js +971 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-persona-controller.test.js +127 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-prompt-composition.test.js +76 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +303 -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 +283 -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 +271 -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/artifact-registry.test.js +511 -0
- package/tests/assistant-runtime.test.js +506 -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/controller-client.test.js +133 -0
- package/tests/deployment.test.js +527 -0
- package/tests/e2e/lifecycle.test.js +120 -0
- package/tests/event-bus-integration.test.js +355 -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 +415 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +223 -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/health-probes.test.js +90 -0
- package/tests/hooks-lifecycle.test.js +364 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/jitsi-agent-bridge.test.js +119 -0
- package/tests/jitsi-helm-integration.test.js +77 -0
- package/tests/jitsi-meeting-controller.test.js +170 -0
- package/tests/jitsi-resource-model.test.js +73 -0
- package/tests/jitsi-sync-controller.test.js +112 -0
- package/tests/kradle-inference-service.test.js +689 -0
- package/tests/kradle.test.js +779 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/model-route-controller.test.js +733 -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 +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/virtual-model-controller.test.js +877 -0
- package/tests/virtual-model-hook-bridge.test.js +384 -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,250 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { clone } from './resource-model.js';
|
|
3
|
+
|
|
4
|
+
export const AGENT_PERMISSION_REVIEW_BOUNDARY = {
|
|
5
|
+
role: 'agent-permission-review',
|
|
6
|
+
scope: 'Deterministic permission review for agent dispatch decisions',
|
|
7
|
+
owns: ['capability expansion', 'grant resolution', 'permission snapshot creation'],
|
|
8
|
+
delegatesTo: ['resource-model'],
|
|
9
|
+
mustNotOwn: ['secret values', 'native K8s API calls', 'runtime execution']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const VALID_APPROVAL_MODES = new Set(['yolo', 'prompt', 'deny']);
|
|
13
|
+
|
|
14
|
+
export function createPermissionReviewer(options = {}) {
|
|
15
|
+
return {
|
|
16
|
+
role: 'agent-permission-review',
|
|
17
|
+
|
|
18
|
+
reviewPermissions({ repository, ref, actor, agentStack, triggerSource, taskKind, runnerPool, toolRefs = [], skillRefs = [], mcpServerRefs = [], contextLabelRefs = [], workspacePolicyRef, isFork = false, resources = {} }) {
|
|
19
|
+
const reasons = [];
|
|
20
|
+
const grants = [];
|
|
21
|
+
const crossOrgDenials = [];
|
|
22
|
+
const untrustedForkWarnings = [];
|
|
23
|
+
|
|
24
|
+
// Step 1 — Resolve AgentStack
|
|
25
|
+
const stacks = resources.AgentStack || [];
|
|
26
|
+
const stack = stacks.find((s) => s.metadata?.name === agentStack);
|
|
27
|
+
if (!stack) {
|
|
28
|
+
return buildDecision({ decision: 'denied', reasons: [{ severity: 'error', message: `AgentStack not found: ${agentStack}` }], grants, capabilities: {}, actor, repository, ref, agentStack, taskKind, crossOrgDenials, untrustedForkWarnings });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Step 1a — Validate approvalMode
|
|
32
|
+
const approvalMode = stack.spec?.approvalMode;
|
|
33
|
+
if (approvalMode !== undefined && !VALID_APPROVAL_MODES.has(approvalMode)) {
|
|
34
|
+
reasons.push({ severity: 'error', message: `Invalid approvalMode '${approvalMode}': must be one of ${[...VALID_APPROVAL_MODES].join(', ')}` });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Step 1b — Deny mode blocks everything immediately
|
|
38
|
+
if (approvalMode === 'deny') {
|
|
39
|
+
reasons.push({ severity: 'error', message: `approvalMode is 'deny': all requests are blocked by policy` });
|
|
40
|
+
return buildDecision({ decision: 'denied', reasons, grants, capabilities: {}, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials, untrustedForkWarnings });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Step 2 — Cross-org denial check
|
|
44
|
+
const agentOrg = stack.spec?.organizationRef;
|
|
45
|
+
if (agentOrg && repository) {
|
|
46
|
+
// repository format: '<org>/<repo>' or just '<repo>'
|
|
47
|
+
const repoParts = repository.split('/');
|
|
48
|
+
const repoOrg = repoParts.length >= 2 ? repoParts[0] : null;
|
|
49
|
+
if (repoOrg && repoOrg !== agentOrg) {
|
|
50
|
+
crossOrgDenials.push({ agentOrg, resourceOrg: repoOrg, resource: repository });
|
|
51
|
+
reasons.push({ severity: 'error', message: `Cross-org access denied: agent org '${agentOrg}' cannot access repository in org '${repoOrg}'` });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Step 3 — Expand capabilities from stack spec
|
|
56
|
+
const capabilities = {
|
|
57
|
+
toolRefs: clone(stack.spec?.toolPolicy ? [stack.spec.toolPolicy] : toolRefs),
|
|
58
|
+
mcpServerRefs: clone(stack.spec?.mcpServerRefs || mcpServerRefs),
|
|
59
|
+
skillRefs: clone(stack.spec?.skillRefs || skillRefs),
|
|
60
|
+
subagentRefs: clone(stack.spec?.subagentRefs || [])
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Step 4 — Untrusted fork detection
|
|
64
|
+
const isForkRef = isFork || /^refs\/pull\/\d+\//.test(ref);
|
|
65
|
+
if (isForkRef) {
|
|
66
|
+
const blockedKinds = ['AgentServiceAccount', 'AgentSecretGrant'];
|
|
67
|
+
untrustedForkWarnings.push({
|
|
68
|
+
ref,
|
|
69
|
+
isFork: true,
|
|
70
|
+
blockedKinds,
|
|
71
|
+
message: `Untrusted fork detected for ref '${ref}': privileged grants restricted`
|
|
72
|
+
});
|
|
73
|
+
reasons.push({ severity: 'warning', message: `Untrusted fork detected for ref '${ref}': privileged grants (${blockedKinds.join(', ')}) are not auto-approved` });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Step 5 — Check runtime identity (AgentServiceAccount)
|
|
77
|
+
const serviceAccountRef = stack.spec?.runtimeIdentity?.serviceAccountRef || stack.spec?.runtimeIdentity;
|
|
78
|
+
const serviceAccounts = resources.AgentServiceAccount || [];
|
|
79
|
+
const serviceAccount = serviceAccounts.find((sa) => sa.metadata?.name === serviceAccountRef);
|
|
80
|
+
if (!serviceAccount) {
|
|
81
|
+
reasons.push({ severity: 'error', message: `Missing AgentServiceAccount: ${serviceAccountRef}` });
|
|
82
|
+
} else {
|
|
83
|
+
const saGrant = { kind: 'AgentServiceAccount', name: serviceAccount.metadata.name, status: 'bound' };
|
|
84
|
+
if (isForkRef) {
|
|
85
|
+
saGrant.status = 'fork-restricted';
|
|
86
|
+
}
|
|
87
|
+
grants.push(saGrant);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Step 6 — Check role bindings
|
|
91
|
+
const roleBindings = resources.AgentRoleBinding || [];
|
|
92
|
+
const matchedBindings = roleBindings.filter((rb) => rb.spec?.subject === serviceAccountRef || rb.spec?.subject === agentStack);
|
|
93
|
+
for (const binding of matchedBindings) {
|
|
94
|
+
grants.push({ kind: 'AgentRoleBinding', name: binding.metadata.name, roleRef: binding.spec?.roleRef, scope: binding.spec?.scope, status: 'bound' });
|
|
95
|
+
}
|
|
96
|
+
if (matchedBindings.length === 0 && serviceAccount) {
|
|
97
|
+
reasons.push({ severity: 'warning', message: `No AgentRoleBinding found for subject: ${serviceAccountRef}` });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Step 7 — Check secret grants
|
|
101
|
+
const secretGrants = resources.AgentSecretGrant || [];
|
|
102
|
+
const neededSecrets = collectSecretNeeds(stack, capabilities, resources);
|
|
103
|
+
for (const need of neededSecrets) {
|
|
104
|
+
const match = secretGrants.find((sg) => {
|
|
105
|
+
if (sg.spec?.subject !== serviceAccountRef && sg.spec?.subject !== agentStack) return false;
|
|
106
|
+
if (need.purpose && sg.spec?.purpose !== need.purpose) return false;
|
|
107
|
+
if (sg.spec?.allowedRepositories && sg.spec.allowedRepositories.length > 0 && !sg.spec.allowedRepositories.includes(repository)) return false;
|
|
108
|
+
if (sg.spec?.allowedRefs && sg.spec.allowedRefs.length > 0 && !sg.spec.allowedRefs.includes(ref)) return false;
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
if (!match) {
|
|
112
|
+
reasons.push({ severity: 'error', message: `Missing AgentSecretGrant for ${need.description} (purpose: ${need.purpose})` });
|
|
113
|
+
} else {
|
|
114
|
+
const grantEntry = { kind: 'AgentSecretGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' };
|
|
115
|
+
if (isForkRef) {
|
|
116
|
+
grantEntry.status = 'fork-restricted';
|
|
117
|
+
} else if (match.spec?.requiredApproval) {
|
|
118
|
+
grantEntry.status = 'requires-approval';
|
|
119
|
+
grantEntry.requiredApproval = match.spec.requiredApproval;
|
|
120
|
+
reasons.push({ severity: 'info', message: `AgentSecretGrant ${match.metadata.name} requires approval: ${match.spec.requiredApproval}` });
|
|
121
|
+
}
|
|
122
|
+
grants.push(grantEntry);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 8 — Check config grants
|
|
127
|
+
const configGrants = resources.AgentConfigGrant || [];
|
|
128
|
+
const neededConfigs = collectConfigNeeds(stack, capabilities, resources);
|
|
129
|
+
for (const need of neededConfigs) {
|
|
130
|
+
const match = configGrants.find((cg) => {
|
|
131
|
+
if (cg.spec?.subject !== serviceAccountRef && cg.spec?.subject !== agentStack) return false;
|
|
132
|
+
if (need.purpose && cg.spec?.purpose !== need.purpose) return false;
|
|
133
|
+
return true;
|
|
134
|
+
});
|
|
135
|
+
if (!match) {
|
|
136
|
+
reasons.push({ severity: 'error', message: `Missing AgentConfigGrant for ${need.description} (purpose: ${need.purpose})` });
|
|
137
|
+
} else {
|
|
138
|
+
grants.push({ kind: 'AgentConfigGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Step 9 — Workspace policy enforcement
|
|
143
|
+
if (workspacePolicyRef) {
|
|
144
|
+
const policies = resources.KradleWorkspacePolicy || [];
|
|
145
|
+
const policy = policies.find((p) => p.metadata?.name === workspacePolicyRef);
|
|
146
|
+
if (policy) {
|
|
147
|
+
// Check maxConcurrentSessions
|
|
148
|
+
if (policy.spec?.maxConcurrentSessions === 0) {
|
|
149
|
+
reasons.push({ severity: 'error', message: `Workspace policy '${workspacePolicyRef}' maxConcurrentSessions is 0: no sessions allowed` });
|
|
150
|
+
}
|
|
151
|
+
// Check deniedTools
|
|
152
|
+
const deniedTools = policy.spec?.deniedTools || [];
|
|
153
|
+
const requestedTools = capabilities.toolRefs;
|
|
154
|
+
for (const tool of requestedTools) {
|
|
155
|
+
if (deniedTools.includes(tool)) {
|
|
156
|
+
reasons.push({ severity: 'error', message: `Tool '${tool}' is denied by workspace policy '${workspacePolicyRef}'` });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Check allowedTools (if specified, only those tools are permitted)
|
|
160
|
+
const allowedTools = policy.spec?.allowedTools;
|
|
161
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
162
|
+
for (const tool of requestedTools) {
|
|
163
|
+
if (!allowedTools.includes(tool)) {
|
|
164
|
+
reasons.push({ severity: 'error', message: `Tool '${tool}' is not in allowedTools for workspace policy '${workspacePolicyRef}'` });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Step 10 — Decision
|
|
172
|
+
const hasErrors = reasons.some((r) => r.severity === 'error');
|
|
173
|
+
const hasApprovals = grants.some((g) => g.status === 'requires-approval');
|
|
174
|
+
let decision;
|
|
175
|
+
if (hasErrors) {
|
|
176
|
+
decision = 'denied';
|
|
177
|
+
} else if (hasApprovals) {
|
|
178
|
+
decision = 'requires-approval';
|
|
179
|
+
} else {
|
|
180
|
+
decision = 'allowed';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials, untrustedForkWarnings });
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
createPermissionSnapshot(reviewResult) {
|
|
187
|
+
const snapshot = clone(reviewResult);
|
|
188
|
+
snapshot.snapshotAt = new Date().toISOString();
|
|
189
|
+
snapshot.frozen = true;
|
|
190
|
+
snapshot.digest = reviewResult.digest;
|
|
191
|
+
return Object.freeze(snapshot);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function collectSecretNeeds(stack, capabilities, resources) {
|
|
197
|
+
const needs = [];
|
|
198
|
+
if (stack.spec?.adapter) {
|
|
199
|
+
needs.push({ description: `model provider for adapter ${stack.spec.adapter}`, purpose: 'model-provider' });
|
|
200
|
+
}
|
|
201
|
+
const mcpServers = resources.AgentMcpServer || [];
|
|
202
|
+
for (const ref of capabilities.mcpServerRefs) {
|
|
203
|
+
const server = mcpServers.find((s) => s.metadata?.name === ref);
|
|
204
|
+
if (server?.spec?.secretRef) {
|
|
205
|
+
needs.push({ description: `MCP server ${ref} secret`, purpose: `mcp-server:${ref}` });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return needs;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function collectConfigNeeds(stack, capabilities, resources) {
|
|
212
|
+
const needs = [];
|
|
213
|
+
const mcpServers = resources.AgentMcpServer || [];
|
|
214
|
+
for (const ref of capabilities.mcpServerRefs) {
|
|
215
|
+
const server = mcpServers.find((s) => s.metadata?.name === ref);
|
|
216
|
+
if (server?.spec?.configMapRef) {
|
|
217
|
+
needs.push({ description: `MCP server ${ref} config`, purpose: `mcp-server:${ref}` });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return needs;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials = [], untrustedForkWarnings = [] }) {
|
|
224
|
+
const result = {
|
|
225
|
+
decision,
|
|
226
|
+
actor,
|
|
227
|
+
repository,
|
|
228
|
+
ref,
|
|
229
|
+
agentStack,
|
|
230
|
+
taskKind,
|
|
231
|
+
capabilities: clone(capabilities),
|
|
232
|
+
grants: clone(grants),
|
|
233
|
+
reasons: clone(reasons),
|
|
234
|
+
crossOrgDenials: clone(crossOrgDenials),
|
|
235
|
+
untrustedForkWarnings: clone(untrustedForkWarnings),
|
|
236
|
+
reviewedAt: new Date().toISOString()
|
|
237
|
+
};
|
|
238
|
+
if (approvalMode !== undefined) {
|
|
239
|
+
result.approvalMode = approvalMode;
|
|
240
|
+
}
|
|
241
|
+
result.digest = computeDigest(result);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function computeDigest(result) {
|
|
246
|
+
const keys = Object.keys(result).filter((k) => k !== 'digest' && k !== 'reviewedAt').sort();
|
|
247
|
+
const canonical = {};
|
|
248
|
+
for (const key of keys) canonical[key] = result[key];
|
|
249
|
+
return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
|
|
250
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { clone, createResource } from './resource-model.js';
|
|
2
|
+
|
|
3
|
+
export const AGENT_PERSONA_CONTROLLER_BOUNDARY = {
|
|
4
|
+
role: 'agent-persona-controller',
|
|
5
|
+
scope: 'Agent identity validation and resolution for personas, definitions, souls, appearance, and voice profiles',
|
|
6
|
+
owns: ['agent identity reference resolution', 'agent profile validation'],
|
|
7
|
+
delegatesTo: ['resource-model', 'agent-prompt-composition'],
|
|
8
|
+
mustNotOwn: ['runtime job creation', 'permission review']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function findByName(resources, kind, name, organizationRef) {
|
|
12
|
+
return (resources?.[kind] || []).find((resource) => {
|
|
13
|
+
if (resource.metadata?.name !== name) return false;
|
|
14
|
+
return !organizationRef || !resource.spec?.organizationRef || resource.spec.organizationRef === organizationRef;
|
|
15
|
+
}) || null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function required(resource, fields) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
for (const field of fields) {
|
|
21
|
+
const value = resource?.spec?.[field];
|
|
22
|
+
if (value === undefined || value === null || value === '') errors.push(`spec.${field} is required`);
|
|
23
|
+
}
|
|
24
|
+
return errors;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validateAgentPersona(resource) {
|
|
28
|
+
const errors = required(resource, ['organizationRef', 'displayName']);
|
|
29
|
+
if (resource?.kind && resource.kind !== 'AgentPersona') errors.push('kind must be AgentPersona');
|
|
30
|
+
return { valid: errors.length === 0, errors };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function validateAgentDefinition(resource) {
|
|
34
|
+
const errors = required(resource, ['organizationRef', 'personaRef', 'stackRef']);
|
|
35
|
+
if (resource?.kind && resource.kind !== 'AgentDefinition') errors.push('kind must be AgentDefinition');
|
|
36
|
+
return { valid: errors.length === 0, errors };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function inlineResource(kind, name, namespace, organizationRef, spec) {
|
|
40
|
+
return createResource(kind, { name, namespace }, { organizationRef, ...(spec || {}) });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveProfileRef({ resources, kind, ref, organizationRef, errors }) {
|
|
44
|
+
if (!ref) return null;
|
|
45
|
+
const found = findByName(resources, kind, ref, organizationRef);
|
|
46
|
+
if (!found) errors.push(`${kind} not found: ${ref}`);
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveAgentPersona(personaRef, { resources = {}, organizationRef = 'default' } = {}) {
|
|
51
|
+
const errors = [];
|
|
52
|
+
const persona = typeof personaRef === 'string' ? findByName(resources, 'AgentPersona', personaRef, organizationRef) : personaRef;
|
|
53
|
+
if (!persona) {
|
|
54
|
+
return { error: true, persona: null, soul: null, appearance: null, voiceProfile: null, errors: [`AgentPersona not found: ${personaRef}`] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const validation = validateAgentPersona(persona);
|
|
58
|
+
errors.push(...validation.errors);
|
|
59
|
+
|
|
60
|
+
const namespace = persona.metadata?.namespace || 'default';
|
|
61
|
+
const name = persona.metadata?.name || 'agent';
|
|
62
|
+
const spec = persona.spec || {};
|
|
63
|
+
|
|
64
|
+
let soul = null;
|
|
65
|
+
if (spec.soul?.inline) {
|
|
66
|
+
soul = inlineResource('AgentSoul', `${name}-inline-soul`, namespace, spec.organizationRef || organizationRef, { personaRef: name, content: spec.soul.inline });
|
|
67
|
+
} else if (spec.soul?.ref) {
|
|
68
|
+
soul = resolveProfileRef({ resources, kind: 'AgentSoul', ref: spec.soul.ref, organizationRef, errors });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let appearance = null;
|
|
72
|
+
if (spec.appearance?.inline) {
|
|
73
|
+
appearance = inlineResource('AgentAppearance', `${name}-inline-appearance`, namespace, spec.organizationRef || organizationRef, { personaRef: name, ...spec.appearance.inline });
|
|
74
|
+
} else if (spec.appearance?.ref) {
|
|
75
|
+
appearance = resolveProfileRef({ resources, kind: 'AgentAppearance', ref: spec.appearance.ref, organizationRef, errors });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let voiceProfile = null;
|
|
79
|
+
if (spec.voiceProfile?.inline) {
|
|
80
|
+
voiceProfile = inlineResource('AgentVoiceProfile', `${name}-inline-voice`, namespace, spec.organizationRef || organizationRef, { personaRef: name, ...spec.voiceProfile.inline });
|
|
81
|
+
} else if (spec.voiceProfile?.ref) {
|
|
82
|
+
voiceProfile = resolveProfileRef({ resources, kind: 'AgentVoiceProfile', ref: spec.voiceProfile.ref, organizationRef, errors });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { error: errors.length > 0, persona, soul, appearance, voiceProfile, errors };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveAgentDefinition(definitionRef, { resources = {}, organizationRef = 'default' } = {}) {
|
|
89
|
+
const errors = [];
|
|
90
|
+
const definition = typeof definitionRef === 'string' ? findByName(resources, 'AgentDefinition', definitionRef, organizationRef) : definitionRef;
|
|
91
|
+
if (!definition) {
|
|
92
|
+
return { error: true, definition: null, persona: null, soul: null, appearance: null, voiceProfile: null, stack: null, errors: [`AgentDefinition not found: ${definitionRef}`] };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const validation = validateAgentDefinition(definition);
|
|
96
|
+
errors.push(...validation.errors);
|
|
97
|
+
const resolvedPersona = resolveAgentPersona(definition.spec?.personaRef, { resources, organizationRef: definition.spec?.organizationRef || organizationRef });
|
|
98
|
+
errors.push(...resolvedPersona.errors);
|
|
99
|
+
|
|
100
|
+
const stackRef = definition.spec?.stackRef;
|
|
101
|
+
const stack = stackRef ? findByName(resources, 'AgentStack', stackRef, definition.spec?.organizationRef || organizationRef) : null;
|
|
102
|
+
if (stackRef && !stack) errors.push(`AgentStack not found: ${stackRef}`);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
error: errors.length > 0,
|
|
106
|
+
definition,
|
|
107
|
+
persona: resolvedPersona.persona,
|
|
108
|
+
soul: resolvedPersona.soul,
|
|
109
|
+
appearance: resolvedPersona.appearance,
|
|
110
|
+
voiceProfile: resolvedPersona.voiceProfile,
|
|
111
|
+
stack,
|
|
112
|
+
errors,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createAgentPersonaController() {
|
|
117
|
+
return {
|
|
118
|
+
role: 'agent-persona-controller',
|
|
119
|
+
validateAgentPersona,
|
|
120
|
+
validateAgentDefinition,
|
|
121
|
+
resolveAgentPersona,
|
|
122
|
+
resolveAgentDefinition,
|
|
123
|
+
summarizeProfile(resolved) {
|
|
124
|
+
return {
|
|
125
|
+
definition: clone(resolved.definition),
|
|
126
|
+
persona: clone(resolved.persona),
|
|
127
|
+
soul: clone(resolved.soul),
|
|
128
|
+
appearance: clone(resolved.appearance),
|
|
129
|
+
voiceProfile: clone(resolved.voiceProfile),
|
|
130
|
+
stack: clone(resolved.stack),
|
|
131
|
+
errors: clone(resolved.errors || []),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Agent Project Controller — Slice 1.2d
|
|
2
|
+
// Manages KradleProject resources: validation, workflow columns, board state, and issue assignment.
|
|
3
|
+
|
|
4
|
+
export const AGENT_PROJECT_CONTROLLER_BOUNDARY = {
|
|
5
|
+
role: 'agent-project-controller',
|
|
6
|
+
scope: 'KradleProject lifecycle: validation, workflow column definitions, board state management, issue assignment tracking',
|
|
7
|
+
owns: ['project validation', 'workflow columns', 'board state', 'default column resolution', 'issue assignment tracking'],
|
|
8
|
+
delegatesTo: ['resource-model'],
|
|
9
|
+
mustNotOwn: ['issue content', 'PR lifecycle', 'dispatch execution', 'Agent Mux sessions']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const VALID_BOARD_STATES = ['active', 'archived'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate a KradleProject resource. Returns { valid, errors }.
|
|
16
|
+
* @param {object} resource
|
|
17
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
18
|
+
*/
|
|
19
|
+
export function validateAgentProject(resource) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
|
|
22
|
+
// Guard against null/undefined resource
|
|
23
|
+
if (resource == null) {
|
|
24
|
+
errors.push('resource must not be null or undefined');
|
|
25
|
+
return { valid: false, errors };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate metadata.name
|
|
29
|
+
if (!resource?.metadata?.name) {
|
|
30
|
+
errors.push('metadata.name is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const spec = resource?.spec || {};
|
|
34
|
+
|
|
35
|
+
// Validate organizationRef
|
|
36
|
+
if (!spec.organizationRef) {
|
|
37
|
+
errors.push('spec.organizationRef is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate workflowColumns — must be a non-empty array
|
|
41
|
+
const cols = spec.workflowColumns;
|
|
42
|
+
if (!Array.isArray(cols) || cols.length === 0) {
|
|
43
|
+
errors.push('spec.workflowColumns must be a non-empty array');
|
|
44
|
+
} else {
|
|
45
|
+
// Check for duplicate column IDs
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const col of cols) {
|
|
48
|
+
if (seen.has(col.id)) {
|
|
49
|
+
errors.push(`spec.workflowColumns contains duplicate column ID: "${col.id}"`);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
seen.add(col.id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate boardState if explicitly set
|
|
57
|
+
const boardState = spec.boardState;
|
|
58
|
+
if (boardState != null && !VALID_BOARD_STATES.includes(boardState)) {
|
|
59
|
+
errors.push(`spec.boardState "${boardState}" is not supported; valid states are: ${VALID_BOARD_STATES.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { valid: errors.length === 0, errors };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Factory that returns a KradleProject controller instance.
|
|
67
|
+
*/
|
|
68
|
+
export function createAgentProjectController() {
|
|
69
|
+
return {
|
|
70
|
+
role: 'agent-project-controller',
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate a KradleProject resource.
|
|
74
|
+
* @param {object} resource
|
|
75
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
76
|
+
*/
|
|
77
|
+
validate(resource) {
|
|
78
|
+
return validateAgentProject(resource);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Return the workflow columns for a project, in order.
|
|
83
|
+
* @param {object} resource
|
|
84
|
+
* @returns {Array<{ id: string, displayName: string, color: string, default?: boolean }>}
|
|
85
|
+
*/
|
|
86
|
+
getWorkflowColumns(resource) {
|
|
87
|
+
const cols = resource?.spec?.workflowColumns;
|
|
88
|
+
if (!Array.isArray(cols)) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return [...cols];
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Return the default column — the one marked `default: true`, or the first column.
|
|
96
|
+
* @param {object} resource
|
|
97
|
+
* @returns {{ id: string, displayName: string, color: string, default?: boolean } | undefined}
|
|
98
|
+
*/
|
|
99
|
+
getDefaultColumn(resource) {
|
|
100
|
+
const cols = Array.isArray(resource?.spec?.workflowColumns)
|
|
101
|
+
? resource.spec.workflowColumns
|
|
102
|
+
: [];
|
|
103
|
+
const marked = cols.find((c) => c.default === true);
|
|
104
|
+
return marked ?? cols[0];
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Return the board state for a project.
|
|
109
|
+
* Defaults to 'active' when spec.boardState is not set.
|
|
110
|
+
* @param {object} resource
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
113
|
+
getBoardState(resource) {
|
|
114
|
+
return resource?.spec?.boardState ?? 'active';
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function addPart(parts, value) {
|
|
2
|
+
if (typeof value === 'string' && value.trim()) parts.push(value.trim());
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function joinList(values) {
|
|
6
|
+
return Array.isArray(values) && values.length > 0 ? values.join(', ') : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function composeAgentSystemPrompt({ soul = null, persona = null, definition = null, stack = null } = {}) {
|
|
10
|
+
const parts = [];
|
|
11
|
+
addPart(parts, soul?.spec?.content);
|
|
12
|
+
|
|
13
|
+
const personaSpec = persona?.spec || {};
|
|
14
|
+
const personaParts = [];
|
|
15
|
+
addPart(personaParts, personaSpec.displayName);
|
|
16
|
+
addPart(personaParts, personaSpec.tagline);
|
|
17
|
+
|
|
18
|
+
const personality = personaSpec.personality || {};
|
|
19
|
+
const personalityParts = [];
|
|
20
|
+
if (personality.communicationStyle) personalityParts.push(`Communication style: ${personality.communicationStyle}`);
|
|
21
|
+
if (personality.tone) personalityParts.push(`Tone: ${personality.tone}`);
|
|
22
|
+
if (personality.explanationDepth) personalityParts.push(`Explanation depth: ${personality.explanationDepth}`);
|
|
23
|
+
if (personality.humorLevel) personalityParts.push(`Humor level: ${personality.humorLevel}`);
|
|
24
|
+
if (personality.language) personalityParts.push(`Language: ${personality.language}`);
|
|
25
|
+
const traits = joinList(personality.traits);
|
|
26
|
+
if (traits) personalityParts.push(`Traits: ${traits}`);
|
|
27
|
+
addPart(personaParts, personalityParts.join('. '));
|
|
28
|
+
|
|
29
|
+
const role = personaSpec.role || {};
|
|
30
|
+
const roleParts = [];
|
|
31
|
+
if (role.title) roleParts.push(`Role: ${role.title}`);
|
|
32
|
+
if (role.domain) roleParts.push(`Domain: ${role.domain}`);
|
|
33
|
+
const expertise = joinList(role.expertise);
|
|
34
|
+
if (expertise) roleParts.push(`Expertise: ${expertise}`);
|
|
35
|
+
addPart(personaParts, roleParts.join('. '));
|
|
36
|
+
|
|
37
|
+
const skills = joinList(personaSpec.skillRefs);
|
|
38
|
+
if (skills) personaParts.push(`Skills: ${skills}`);
|
|
39
|
+
const knowledge = joinList(personaSpec.knowledgeRefs);
|
|
40
|
+
if (knowledge) personaParts.push(`Knowledge: ${knowledge}`);
|
|
41
|
+
addPart(parts, personaParts.join('\n'));
|
|
42
|
+
|
|
43
|
+
addPart(parts, definition?.spec?.roleContext);
|
|
44
|
+
addPart(parts, stack?.spec?.systemPrompt);
|
|
45
|
+
return parts.join('\n\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function composeAgentPrompt({ soul = null, persona = null, definition = null, stack = null } = {}) {
|
|
49
|
+
const legacyPrompts = persona?.spec?.legacyPrompts || {};
|
|
50
|
+
return {
|
|
51
|
+
system: composeAgentSystemPrompt({ soul, persona, definition, stack }) || null,
|
|
52
|
+
developer: legacyPrompts.developerPrompt || stack?.spec?.developerPrompt || null,
|
|
53
|
+
task: legacyPrompts.taskPrompt || stack?.spec?.taskPrompt || null,
|
|
54
|
+
};
|
|
55
|
+
}
|