agent-devkit 0.2.0 → 0.3.1
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/README.md +66 -13
- package/bin/agent.mjs +133 -7
- package/package.json +1 -1
- package/runtime/README.md +205 -13
- package/runtime/agent +31 -5
- package/runtime/agents/README.md +18 -0
- package/runtime/agents/contribution-reviewer/AGENTS.md +8 -0
- package/runtime/agents/contribution-reviewer/README.md +8 -0
- package/runtime/agents/contribution-reviewer/agent.yaml +40 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/capability.yaml +27 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/capability.yaml +25 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/workflow.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/capability.yaml +26 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/infra/README.md +6 -0
- package/runtime/agents/contribution-reviewer/knowledge/context.md +8 -0
- package/runtime/agents/contribution-reviewer/knowledge/system.md +8 -0
- package/runtime/agents/contribution-reviewer/templates/README.md +3 -0
- package/runtime/agents/knowledge-author/AGENTS.md +7 -0
- package/runtime/agents/knowledge-author/README.md +7 -0
- package/runtime/agents/knowledge-author/agent.yaml +37 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/capability.yaml +30 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-author/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-author/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-author/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-author/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/AGENTS.md +7 -0
- package/runtime/agents/knowledge-curator/README.md +6 -0
- package/runtime/agents/knowledge-curator/agent.yaml +37 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/capability.yaml +29 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-curator/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-curator/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-curator/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/AGENTS.md +8 -0
- package/runtime/agents/knowledge-infra-builder/README.md +8 -0
- package/runtime/agents/knowledge-infra-builder/agent.yaml +38 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/capability.yaml +30 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-infra-builder/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-infra-builder/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/AGENTS.md +7 -0
- package/runtime/agents/knowledge-owner/README.md +6 -0
- package/runtime/agents/knowledge-owner/agent.yaml +37 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/capability.yaml +28 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-owner/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-owner/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-owner/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/AGENTS.md +7 -0
- package/runtime/agents/knowledge-reviewer/README.md +7 -0
- package/runtime/agents/knowledge-reviewer/agent.yaml +36 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/capability.yaml +26 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-reviewer/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-reviewer/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-reviewer/templates/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/AGENTS.md +5 -0
- package/runtime/agents/local-memory-manager/README.md +7 -0
- package/runtime/agents/local-memory-manager/agent.yaml +38 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/workflow.md +6 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/workflow.md +5 -0
- package/runtime/agents/local-memory-manager/infra/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/knowledge/context.md +4 -0
- package/runtime/agents/local-memory-manager/knowledge/system.md +4 -0
- package/runtime/agents/local-memory-manager/templates/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/AGENTS.md +7 -0
- package/runtime/agents/memory-sync-manager/README.md +7 -0
- package/runtime/agents/memory-sync-manager/agent.yaml +37 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/capability.yaml +29 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/decision-rules.md +6 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/workflow.md +7 -0
- package/runtime/agents/memory-sync-manager/infra/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/knowledge/context.md +4 -0
- package/runtime/agents/memory-sync-manager/knowledge/system.md +4 -0
- package/runtime/agents/memory-sync-manager/templates/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/AGENTS.md +5 -0
- package/runtime/agents/shared-memory-curator/README.md +6 -0
- package/runtime/agents/shared-memory-curator/agent.yaml +38 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/infra/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/knowledge/context.md +5 -0
- package/runtime/agents/shared-memory-curator/knowledge/system.md +4 -0
- package/runtime/agents/shared-memory-curator/templates/.gitkeep +1 -0
- package/runtime/cli/README.md +47 -8
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/agent_registry.py +4 -2
- package/runtime/cli/aikit/agentic_commands.py +158 -0
- package/runtime/cli/aikit/app_home.py +2 -0
- package/runtime/cli/aikit/audit.py +16 -6
- package/runtime/cli/aikit/catalog.py +278 -8
- package/runtime/cli/aikit/cli_dispatch.py +489 -13
- package/runtime/cli/aikit/cli_parser.py +146 -8
- package/runtime/cli/aikit/contribution.py +132 -2
- package/runtime/cli/aikit/doctor_runtime.py +85 -0
- package/runtime/cli/aikit/embedded_mini_brain.py +351 -0
- package/runtime/cli/aikit/eval.py +356 -10
- package/runtime/cli/aikit/human_output.py +310 -4
- package/runtime/cli/aikit/interactive_wizard.py +146 -0
- package/runtime/cli/aikit/knowledge_base.py +1067 -0
- package/runtime/cli/aikit/llm.py +40 -6
- package/runtime/cli/aikit/local_artifacts.py +444 -0
- package/runtime/cli/aikit/local_llm.py +176 -0
- package/runtime/cli/aikit/local_llm_operator.py +15 -5
- package/runtime/cli/aikit/main.py +15 -0
- package/runtime/cli/aikit/mcp_manifest.py +798 -0
- package/runtime/cli/aikit/mcp_tools.py +643 -5
- package/runtime/cli/aikit/memory.py +405 -0
- package/runtime/cli/aikit/mini_brain.py +56 -25
- package/runtime/cli/aikit/model_router.py +42 -9
- package/runtime/cli/aikit/natural_prompt_runtime.py +194 -2
- package/runtime/cli/aikit/ollama.py +64 -15
- package/runtime/cli/aikit/onboarding.py +551 -0
- package/runtime/cli/aikit/output.py +67 -0
- package/runtime/cli/aikit/prompt_injection.py +12 -1
- package/runtime/cli/aikit/review_gate.py +14 -2
- package/runtime/cli/aikit/roadmap_cli.py +1 -1
- package/runtime/cli/aikit/secrets.py +3 -2
- package/runtime/cli/aikit/setup_wizard_payload.py +3 -0
- package/runtime/cli/aikit/shared_memory.py +415 -0
- package/runtime/cli/aikit/specialist_readiness.py +152 -0
- package/runtime/cli/aikit/tasks.py +104 -1
- package/runtime/cli/aikit/team.py +380 -0
- package/runtime/cli/aikit/toolchain.py +7 -2
- package/runtime/cli/aikit/workflows.py +115 -14
- package/runtime/models/qwen2.5-0.5b-instruct/manifest.json +30 -0
- package/runtime/providers/knowledge-github.yaml +40 -0
- package/runtime/providers/knowledge-google-drive.yaml +32 -0
- package/runtime/providers/knowledge-local.yaml +26 -0
- package/runtime/providers/knowledge-notion.yaml +32 -0
- package/runtime/providers/knowledge-obsidian.yaml +24 -0
- package/runtime/providers/knowledge-onedrive.yaml +36 -0
- package/runtime/providers/knowledge-s3.yaml +45 -0
- package/runtime/providers/knowledge-sharepoint.yaml +39 -0
- package/runtime/providers/knowledge-supabase.yaml +43 -0
- package/runtime/providers/knowledge-vector.yaml +39 -0
- package/runtime/requirements.txt +6 -0
- package/runtime/scripts/docker-cli-qa.sh +453 -0
- package/runtime/scripts/release-catalog-snapshot.json +55 -4
- package/runtime/scripts/release-gate.py +54 -13
- package/runtime/tooling/toolchain.yaml +92 -0
- package/runtime/vendor/skills/napkin/napkin.md +21 -7
- package/runtime/workflows/azure-card-analysis/README.md +3 -0
- package/runtime/workflows/azure-card-analysis/workflow.yaml +30 -0
- package/runtime/workflows/daily-pr-review/README.md +3 -0
- package/runtime/workflows/daily-pr-review/workflow.yaml +31 -0
- package/runtime/workflows/incident-analysis/README.md +3 -0
- package/runtime/workflows/incident-analysis/workflow.yaml +33 -0
- package/runtime/workflows/release-prep/README.md +3 -0
- package/runtime/workflows/release-prep/workflow.yaml +30 -0
|
@@ -24,13 +24,13 @@ def build_review_gate(
|
|
|
24
24
|
if route:
|
|
25
25
|
required = True
|
|
26
26
|
reasons.append("deterministic-route")
|
|
27
|
-
if model_plan and (model_plan
|
|
27
|
+
if model_plan and local_worker_review_required(model_plan):
|
|
28
28
|
required = True
|
|
29
29
|
reasons.append("local-llm")
|
|
30
30
|
if model_plan and model_plan.get("strategy") == "human":
|
|
31
31
|
required = True
|
|
32
32
|
reasons.append("human-strategy")
|
|
33
|
-
if model_plan and model_plan
|
|
33
|
+
if model_plan and mini_brain_review_required(model_plan):
|
|
34
34
|
required = True
|
|
35
35
|
reasons.append("mini-brain")
|
|
36
36
|
if model_plan and model_plan.get("risk") == "high":
|
|
@@ -50,6 +50,18 @@ def build_review_gate(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def local_worker_review_required(model_plan: dict[str, Any]) -> bool:
|
|
54
|
+
if not (model_plan.get("local_llm_selected") or model_plan.get("local_llm_recommended")):
|
|
55
|
+
return False
|
|
56
|
+
return model_plan.get("local_llm_provider") == "ollama" or model_plan.get("risk") != "low"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def mini_brain_review_required(model_plan: dict[str, Any]) -> bool:
|
|
60
|
+
if model_plan.get("strategy") != "mini-brain":
|
|
61
|
+
return False
|
|
62
|
+
return model_plan.get("risk") != "low" or model_plan.get("local_llm_provider") == "ollama"
|
|
63
|
+
|
|
64
|
+
|
|
53
65
|
def mark_reviewed(payload: dict[str, Any], *, reviewer: str | None = None, notes: str | None = None) -> dict[str, Any]:
|
|
54
66
|
gate = dict(payload)
|
|
55
67
|
if gate.get("required"):
|
|
@@ -29,7 +29,7 @@ def roadmap_payload(root: Path | None = None, *, phase: int | None = None, probl
|
|
|
29
29
|
"kind": "roadmap",
|
|
30
30
|
"schema_version": ROADMAP_SCHEMA_VERSION,
|
|
31
31
|
"status": "ok",
|
|
32
|
-
"version_scope": "v0.
|
|
32
|
+
"version_scope": "v0.3.0",
|
|
33
33
|
"source": "cli.aikit.roadmap",
|
|
34
34
|
"active_problems": active,
|
|
35
35
|
"preteridos": sorted(preteridos),
|
|
@@ -53,10 +53,11 @@ def add_secret_reference(provider: str, key: str, *, env: str | None) -> dict[st
|
|
|
53
53
|
raise DevKitError("secrets reference add requires --env VAR_NAME")
|
|
54
54
|
data = load_secret_references()
|
|
55
55
|
refs = [item for item in data["references"] if not (item["provider"] == provider and item["key"] == key)]
|
|
56
|
-
|
|
56
|
+
reference = {"provider": provider, "key": key, "backend": "env", "env": env, "value_stored": False}
|
|
57
|
+
refs.append(reference)
|
|
57
58
|
data["references"] = sorted(refs, key=lambda item: (item["provider"], item["key"]))
|
|
58
59
|
save_secret_references(data)
|
|
59
|
-
return {"kind": "secret-reference", "status": "saved", "reference":
|
|
60
|
+
return {"kind": "secret-reference", "status": "saved", "reference": reference, "value_stored": False}
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def list_secret_references() -> dict[str, Any]:
|
|
@@ -16,6 +16,9 @@ def persist_setup_wizard_payload(
|
|
|
16
16
|
wizard = payload.get("setup_wizard")
|
|
17
17
|
if not isinstance(wizard, dict) or wizard.get("wizard_id"):
|
|
18
18
|
return payload
|
|
19
|
+
if wizard.get("status") == "denied-by-user":
|
|
20
|
+
payload["next_question"] = wizard.get("next_question")
|
|
21
|
+
return payload
|
|
19
22
|
persisted = create_provider_wizard(
|
|
20
23
|
wizard,
|
|
21
24
|
execution_plan=execution_plan or payload.get("execution_plan"),
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""Shared memory workspaces with owner-reviewed contributions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import secrets
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from cli.aikit.app_home import app_home, ensure_app_home
|
|
12
|
+
from cli.aikit.errors import DevKitError
|
|
13
|
+
from cli.aikit.knowledge_base import sanitize_snapshot_content, scan_text
|
|
14
|
+
from cli.aikit.prompt_injection import external_content_block
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
SHARED_MEMORY_SCHEMA_VERSION = "agent-devkit.shared-memory/v1"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def shared_memory_home() -> Path:
|
|
21
|
+
ensure_app_home()
|
|
22
|
+
path = app_home() / "shared-memory"
|
|
23
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def shared_memory_create(title: str | None = None) -> dict[str, Any]:
|
|
28
|
+
memory_id = slugify(title or "shared-memory")
|
|
29
|
+
root = unique_workspace_path(memory_id)
|
|
30
|
+
for relative in ("incoming", "reviews", "accepted", "rejected", "audit"):
|
|
31
|
+
(root / relative).mkdir(parents=True, exist_ok=True)
|
|
32
|
+
owner_key = new_key("own")
|
|
33
|
+
contributor_key = new_key("contrib")
|
|
34
|
+
manifest = {
|
|
35
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
36
|
+
"id": root.name,
|
|
37
|
+
"title": title or root.name,
|
|
38
|
+
"owner": "local",
|
|
39
|
+
"owner_key": owner_key,
|
|
40
|
+
"contributor_key": contributor_key,
|
|
41
|
+
"share_url": root.as_uri(),
|
|
42
|
+
"created_at": now_iso(),
|
|
43
|
+
"updated_at": now_iso(),
|
|
44
|
+
"policy": {
|
|
45
|
+
"contributors": "key-required",
|
|
46
|
+
"publish": "owner-review-required",
|
|
47
|
+
"accepted_visibility": "readable-by-url-holder",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
write_json(root / "manifest.json", manifest)
|
|
51
|
+
return {
|
|
52
|
+
"kind": "shared-memory",
|
|
53
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
54
|
+
"status": "created",
|
|
55
|
+
"memory": public_manifest(manifest),
|
|
56
|
+
"path": str(root),
|
|
57
|
+
"owner_access": {
|
|
58
|
+
"key": owner_key,
|
|
59
|
+
"role": "owner",
|
|
60
|
+
"usage": "Required to publish reviewed submissions with --yes.",
|
|
61
|
+
},
|
|
62
|
+
"contributor_access": {
|
|
63
|
+
"url": manifest["share_url"],
|
|
64
|
+
"key": contributor_key,
|
|
65
|
+
"role": "contributor",
|
|
66
|
+
},
|
|
67
|
+
"next_steps": [
|
|
68
|
+
"Share contributor_access.url and contributor_access.key with another agent.",
|
|
69
|
+
"Review submissions with `agent memory review <memory-id> <submission-id>`.",
|
|
70
|
+
"Publish approved submissions with `agent memory publish <memory-id> <submission-id> --yes`.",
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def shared_memory_list() -> dict[str, Any]:
|
|
76
|
+
items = []
|
|
77
|
+
for manifest_path in sorted(shared_memory_home().glob("*/manifest.json")):
|
|
78
|
+
try:
|
|
79
|
+
manifest = read_json(manifest_path)
|
|
80
|
+
except DevKitError:
|
|
81
|
+
continue
|
|
82
|
+
items.append(public_manifest(manifest))
|
|
83
|
+
return {
|
|
84
|
+
"kind": "shared-memories",
|
|
85
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
86
|
+
"status": "ok",
|
|
87
|
+
"home": str(shared_memory_home()),
|
|
88
|
+
"items": items,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def shared_memory_status(memory_id: str | None) -> dict[str, Any]:
|
|
93
|
+
root, manifest = require_workspace(memory_id)
|
|
94
|
+
return {
|
|
95
|
+
"kind": "shared-memory",
|
|
96
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
97
|
+
"status": "ok",
|
|
98
|
+
"memory": public_manifest(manifest),
|
|
99
|
+
"submissions": {
|
|
100
|
+
"pending": count_files(root / "incoming"),
|
|
101
|
+
"accepted": count_files(root / "accepted"),
|
|
102
|
+
"rejected": count_files(root / "rejected"),
|
|
103
|
+
},
|
|
104
|
+
"path": str(root),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def shared_memory_read(memory_id: str | None, entry_id: str | None = None, *, contributor_key: str | None = None) -> dict[str, Any]:
|
|
109
|
+
root, manifest = require_workspace(memory_id)
|
|
110
|
+
if contributor_key and contributor_key != manifest.get("contributor_key"):
|
|
111
|
+
return {
|
|
112
|
+
"kind": "shared-memory-read",
|
|
113
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
114
|
+
"status": "blocked",
|
|
115
|
+
"ok": False,
|
|
116
|
+
"reason": "invalid-contributor-key",
|
|
117
|
+
"memory_id": manifest.get("id"),
|
|
118
|
+
"exit_code": 2,
|
|
119
|
+
}
|
|
120
|
+
role = "contributor" if contributor_key else "owner"
|
|
121
|
+
accepted = root / "accepted"
|
|
122
|
+
if entry_id:
|
|
123
|
+
sid = require_id(entry_id, "entry id")
|
|
124
|
+
path = accepted / f"{sid}.md"
|
|
125
|
+
if not path.exists():
|
|
126
|
+
raise DevKitError(f"shared memory accepted entry not found: {sid}")
|
|
127
|
+
content = path.read_text(encoding="utf-8", errors="replace")
|
|
128
|
+
return {
|
|
129
|
+
"kind": "shared-memory-read",
|
|
130
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
131
|
+
"status": "ok",
|
|
132
|
+
"memory_id": manifest.get("id"),
|
|
133
|
+
"role": role,
|
|
134
|
+
"entry_id": sid,
|
|
135
|
+
"path": str(path),
|
|
136
|
+
"content": content,
|
|
137
|
+
"items": [accepted_item(root, path)],
|
|
138
|
+
}
|
|
139
|
+
items = [accepted_item(root, path) for path in sorted(accepted.glob("*.md"))]
|
|
140
|
+
return {
|
|
141
|
+
"kind": "shared-memory-read",
|
|
142
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
143
|
+
"status": "ok",
|
|
144
|
+
"memory_id": manifest.get("id"),
|
|
145
|
+
"role": role,
|
|
146
|
+
"path": str(accepted),
|
|
147
|
+
"count": len(items),
|
|
148
|
+
"items": items,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def shared_memory_submit(
|
|
153
|
+
memory_id: str | None,
|
|
154
|
+
*,
|
|
155
|
+
title: str | None,
|
|
156
|
+
content: str | None,
|
|
157
|
+
contributor_key: str | None,
|
|
158
|
+
) -> dict[str, Any]:
|
|
159
|
+
root, manifest = require_workspace(memory_id)
|
|
160
|
+
if not contributor_key or contributor_key != manifest.get("contributor_key"):
|
|
161
|
+
return {
|
|
162
|
+
"kind": "shared-memory-submission",
|
|
163
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
164
|
+
"status": "blocked",
|
|
165
|
+
"ok": False,
|
|
166
|
+
"reason": "invalid-contributor-key",
|
|
167
|
+
"memory_id": manifest.get("id"),
|
|
168
|
+
"exit_code": 2,
|
|
169
|
+
}
|
|
170
|
+
if not content:
|
|
171
|
+
raise DevKitError("memory submit requires --content")
|
|
172
|
+
submission_id = unique_submission_id(title or "submission", root / "incoming")
|
|
173
|
+
block = external_content_block("shared-memory-submission", "markdown", content)
|
|
174
|
+
findings = scan_text(content)
|
|
175
|
+
metadata = {
|
|
176
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
177
|
+
"memory_id": manifest.get("id"),
|
|
178
|
+
"submission_id": submission_id,
|
|
179
|
+
"title": title or submission_id,
|
|
180
|
+
"status": "pending",
|
|
181
|
+
"created_at": now_iso(),
|
|
182
|
+
"findings": findings,
|
|
183
|
+
"prompt_injection": {
|
|
184
|
+
"severity": block["severity"],
|
|
185
|
+
"markers": block["detected_injection_markers"],
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
(root / "incoming" / f"{submission_id}.md").write_text(sanitize_snapshot_content(content).strip() + "\n", encoding="utf-8")
|
|
189
|
+
write_json(root / "incoming" / f"{submission_id}.json", metadata)
|
|
190
|
+
return {
|
|
191
|
+
"kind": "shared-memory-submission",
|
|
192
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
193
|
+
"status": "pending",
|
|
194
|
+
"ok": True,
|
|
195
|
+
"memory_id": manifest.get("id"),
|
|
196
|
+
"submission_id": submission_id,
|
|
197
|
+
"path": str(root / "incoming" / f"{submission_id}.md"),
|
|
198
|
+
"review_required": True,
|
|
199
|
+
"findings": findings,
|
|
200
|
+
"prompt_injection": metadata["prompt_injection"],
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def shared_memory_review(memory_id: str | None, submission_id: str | None, *, persist: bool = True) -> dict[str, Any]:
|
|
205
|
+
root, manifest = require_workspace(memory_id)
|
|
206
|
+
sid = require_id(submission_id, "submission id")
|
|
207
|
+
content_path = root / "incoming" / f"{sid}.md"
|
|
208
|
+
if not content_path.exists():
|
|
209
|
+
raise DevKitError(f"shared memory submission not found: {sid}")
|
|
210
|
+
metadata = read_submission_metadata(root, sid)
|
|
211
|
+
submission_findings = metadata.get("findings") if isinstance(metadata.get("findings"), list) else []
|
|
212
|
+
content = content_path.read_text(encoding="utf-8", errors="replace")
|
|
213
|
+
block = external_content_block(f"shared-memory:{sid}", "markdown", content)
|
|
214
|
+
findings = [*submission_findings]
|
|
215
|
+
if block["severity"] != "none":
|
|
216
|
+
findings.append(
|
|
217
|
+
{
|
|
218
|
+
"reason": "prompt-injection",
|
|
219
|
+
"severity": block["severity"],
|
|
220
|
+
"markers": block["detected_injection_markers"],
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
passed = not findings
|
|
224
|
+
review = {
|
|
225
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
226
|
+
"memory_id": manifest.get("id"),
|
|
227
|
+
"submission_id": sid,
|
|
228
|
+
"status": "approved" if passed else "rejected",
|
|
229
|
+
"findings": findings,
|
|
230
|
+
"reviewed_at": now_iso(),
|
|
231
|
+
"prompt_injection": {
|
|
232
|
+
"severity": block["severity"],
|
|
233
|
+
"markers": block["detected_injection_markers"],
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
review_path = None
|
|
237
|
+
if persist:
|
|
238
|
+
review_path = root / "reviews" / f"{sid}.json"
|
|
239
|
+
write_json(review_path, review)
|
|
240
|
+
return {
|
|
241
|
+
"kind": "shared-memory-review",
|
|
242
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
243
|
+
"status": review["status"],
|
|
244
|
+
"memory_id": manifest.get("id"),
|
|
245
|
+
"submission_id": sid,
|
|
246
|
+
"review": review,
|
|
247
|
+
"persisted": persist,
|
|
248
|
+
"path": str(review_path) if review_path else None,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def shared_memory_publish(
|
|
253
|
+
memory_id: str | None,
|
|
254
|
+
submission_id: str | None,
|
|
255
|
+
*,
|
|
256
|
+
yes: bool = False,
|
|
257
|
+
owner_key: str | None = None,
|
|
258
|
+
) -> dict[str, Any]:
|
|
259
|
+
root, manifest = require_workspace(memory_id)
|
|
260
|
+
sid = require_id(submission_id, "submission id")
|
|
261
|
+
if yes and owner_key != manifest.get("owner_key"):
|
|
262
|
+
return {
|
|
263
|
+
"kind": "shared-memory-publish",
|
|
264
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
265
|
+
"status": "blocked",
|
|
266
|
+
"memory_id": manifest.get("id"),
|
|
267
|
+
"submission_id": sid,
|
|
268
|
+
"reason": "owner_key_required",
|
|
269
|
+
"exit_code": 2,
|
|
270
|
+
}
|
|
271
|
+
review = shared_memory_review(manifest.get("id"), sid, persist=yes)
|
|
272
|
+
if review["status"] != "approved":
|
|
273
|
+
return {
|
|
274
|
+
"kind": "shared-memory-publish",
|
|
275
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
276
|
+
"status": "blocked",
|
|
277
|
+
"memory_id": manifest.get("id"),
|
|
278
|
+
"submission_id": sid,
|
|
279
|
+
"review": review,
|
|
280
|
+
"reason": "review-rejected",
|
|
281
|
+
}
|
|
282
|
+
if not yes:
|
|
283
|
+
return {
|
|
284
|
+
"kind": "shared-memory-publish",
|
|
285
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
286
|
+
"status": "planned",
|
|
287
|
+
"memory_id": manifest.get("id"),
|
|
288
|
+
"submission_id": sid,
|
|
289
|
+
"review": review,
|
|
290
|
+
"next_steps": ["Re-run with `--yes --owner-key <owner-key>` to publish into accepted shared memory."],
|
|
291
|
+
}
|
|
292
|
+
source = root / "incoming" / f"{sid}.md"
|
|
293
|
+
target = root / "accepted" / f"{sid}.md"
|
|
294
|
+
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
|
|
295
|
+
source.unlink(missing_ok=True)
|
|
296
|
+
(root / "incoming" / f"{sid}.json").unlink(missing_ok=True)
|
|
297
|
+
manifest["updated_at"] = now_iso()
|
|
298
|
+
write_json(root / "manifest.json", manifest)
|
|
299
|
+
return {
|
|
300
|
+
"kind": "shared-memory-publish",
|
|
301
|
+
"schema_version": SHARED_MEMORY_SCHEMA_VERSION,
|
|
302
|
+
"status": "published",
|
|
303
|
+
"memory_id": manifest.get("id"),
|
|
304
|
+
"submission_id": sid,
|
|
305
|
+
"path": str(target),
|
|
306
|
+
"review": review,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def require_workspace(memory_id: str | None) -> tuple[Path, dict[str, Any]]:
|
|
311
|
+
item_id = require_id(memory_id, "memory id")
|
|
312
|
+
root = shared_memory_home() / item_id
|
|
313
|
+
manifest_path = root / "manifest.json"
|
|
314
|
+
if not manifest_path.exists():
|
|
315
|
+
raise DevKitError(f"shared memory not found: {item_id}")
|
|
316
|
+
return root, read_json(manifest_path)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def public_manifest(manifest: dict[str, Any]) -> dict[str, Any]:
|
|
320
|
+
payload = {
|
|
321
|
+
"id": manifest.get("id"),
|
|
322
|
+
"title": manifest.get("title"),
|
|
323
|
+
"owner": manifest.get("owner"),
|
|
324
|
+
"share_url": manifest.get("share_url"),
|
|
325
|
+
"created_at": manifest.get("created_at"),
|
|
326
|
+
"updated_at": manifest.get("updated_at"),
|
|
327
|
+
"policy": manifest.get("policy") or {},
|
|
328
|
+
"contributor_key_available": bool(manifest.get("contributor_key")),
|
|
329
|
+
}
|
|
330
|
+
return payload
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def accepted_item(root: Path, path: Path) -> dict[str, Any]:
|
|
334
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
335
|
+
return {
|
|
336
|
+
"id": path.stem,
|
|
337
|
+
"title": title_for(path, text),
|
|
338
|
+
"path": str(path.relative_to(root)),
|
|
339
|
+
"bytes": path.stat().st_size,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def title_for(path: Path, text: str) -> str:
|
|
344
|
+
for line in text.splitlines():
|
|
345
|
+
stripped = line.strip()
|
|
346
|
+
if stripped.startswith("#"):
|
|
347
|
+
return stripped.lstrip("#").strip() or path.stem
|
|
348
|
+
return path.stem
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def unique_workspace_path(base_id: str) -> Path:
|
|
352
|
+
home = shared_memory_home()
|
|
353
|
+
candidate = home / base_id
|
|
354
|
+
if not candidate.exists():
|
|
355
|
+
return candidate
|
|
356
|
+
index = 2
|
|
357
|
+
while (home / f"{base_id}-{index}").exists():
|
|
358
|
+
index += 1
|
|
359
|
+
return home / f"{base_id}-{index}"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def unique_submission_id(title: str, folder: Path) -> str:
|
|
363
|
+
base = slugify(title)
|
|
364
|
+
candidate = base
|
|
365
|
+
index = 2
|
|
366
|
+
while (folder / f"{candidate}.md").exists() or (folder / f"{candidate}.json").exists():
|
|
367
|
+
candidate = f"{base}-{index}"
|
|
368
|
+
index += 1
|
|
369
|
+
return candidate
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def require_id(value: str | None, label: str) -> str:
|
|
373
|
+
item_id = slugify(value or "")
|
|
374
|
+
if not item_id:
|
|
375
|
+
raise DevKitError(f"{label} is required")
|
|
376
|
+
return item_id
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def slugify(value: str) -> str:
|
|
380
|
+
import re
|
|
381
|
+
|
|
382
|
+
slug = re.sub(r"[^a-zA-Z0-9]+", "-", str(value).strip().lower()).strip("-")
|
|
383
|
+
return slug
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def new_key(prefix: str) -> str:
|
|
387
|
+
return f"{prefix}_{secrets.token_urlsafe(24)}"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def now_iso() -> str:
|
|
391
|
+
return datetime.now(timezone.utc).isoformat()
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def count_files(path: Path) -> int:
|
|
395
|
+
return len([item for item in path.glob("*.md") if item.is_file()])
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def read_json(path: Path) -> dict[str, Any]:
|
|
399
|
+
try:
|
|
400
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
401
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
402
|
+
raise DevKitError(f"invalid shared memory file: {path}") from exc
|
|
403
|
+
return payload if isinstance(payload, dict) else {}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def read_submission_metadata(root: Path, submission_id: str) -> dict[str, Any]:
|
|
407
|
+
path = root / "incoming" / f"{submission_id}.json"
|
|
408
|
+
if not path.exists():
|
|
409
|
+
return {}
|
|
410
|
+
return read_json(path)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
414
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
415
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Specialist agent readiness summaries for onboarding and doctor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter, defaultdict
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from cli.aikit.agent_registry import load_agent_registry
|
|
10
|
+
from cli.aikit.sources import list_sources, source_status
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SPECIALIST_READINESS_SCHEMA_VERSION = "agent-devkit.specialist-readiness/v1"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def specialist_readiness(root: Path) -> dict[str, Any]:
|
|
17
|
+
registry = load_agent_registry(root)
|
|
18
|
+
capabilities = registry.get("capabilities") if isinstance(registry.get("capabilities"), dict) else {}
|
|
19
|
+
agents = registry.get("agents") if isinstance(registry.get("agents"), dict) else {}
|
|
20
|
+
source_summary = configured_source_summary()
|
|
21
|
+
configured_providers = set(source_summary["configured_providers"])
|
|
22
|
+
provider_capabilities: Counter[str] = Counter()
|
|
23
|
+
agent_providers: dict[str, Counter[str]] = defaultdict(Counter)
|
|
24
|
+
agent_capabilities: Counter[str] = Counter()
|
|
25
|
+
source_enabled_capabilities: Counter[str] = Counter()
|
|
26
|
+
|
|
27
|
+
for capability in capabilities.values():
|
|
28
|
+
if not isinstance(capability, dict):
|
|
29
|
+
continue
|
|
30
|
+
agent_id = str(capability.get("agent_id") or "")
|
|
31
|
+
if not agent_id:
|
|
32
|
+
continue
|
|
33
|
+
provider = str(capability.get("provider") or "").strip()
|
|
34
|
+
if provider:
|
|
35
|
+
provider_capabilities[provider] += 1
|
|
36
|
+
agent_providers[agent_id][provider] += 1
|
|
37
|
+
agent_capabilities[agent_id] += 1
|
|
38
|
+
source_contract = capability.get("source_contract") if isinstance(capability.get("source_contract"), dict) else {}
|
|
39
|
+
if source_contract.get("enabled") or source_contract.get("supported"):
|
|
40
|
+
source_enabled_capabilities[agent_id] += 1
|
|
41
|
+
|
|
42
|
+
items = [
|
|
43
|
+
agent_readiness_item(
|
|
44
|
+
agent_id,
|
|
45
|
+
agents.get(agent_id) if isinstance(agents.get(agent_id), dict) else {},
|
|
46
|
+
providers,
|
|
47
|
+
configured_providers=configured_providers,
|
|
48
|
+
source_enabled_count=source_enabled_capabilities.get(agent_id, 0),
|
|
49
|
+
)
|
|
50
|
+
for agent_id, providers in sorted(agent_providers.items())
|
|
51
|
+
]
|
|
52
|
+
missing_provider_counts: Counter[str] = Counter()
|
|
53
|
+
for item in items:
|
|
54
|
+
missing_provider_counts.update(item["missing_providers"])
|
|
55
|
+
|
|
56
|
+
blocked = [item for item in items if item["status"] == "needs-setup"]
|
|
57
|
+
partial = [item for item in items if item["status"] == "partial"]
|
|
58
|
+
ready = [item for item in items if item["status"] == "ready"]
|
|
59
|
+
status = "ready"
|
|
60
|
+
if blocked:
|
|
61
|
+
status = "needs-setup"
|
|
62
|
+
elif partial:
|
|
63
|
+
status = "partial"
|
|
64
|
+
return {
|
|
65
|
+
"kind": "specialist-readiness",
|
|
66
|
+
"schema_version": SPECIALIST_READINESS_SCHEMA_VERSION,
|
|
67
|
+
"status": status,
|
|
68
|
+
"agents_total": len(agents),
|
|
69
|
+
"capabilities_total": len(capabilities),
|
|
70
|
+
"agents_with_provider_requirements": len(items),
|
|
71
|
+
"ready_agents": len(ready),
|
|
72
|
+
"partial_agents": len(partial),
|
|
73
|
+
"needs_setup_agents": len(blocked),
|
|
74
|
+
"providers_required": [
|
|
75
|
+
{"id": provider, "capabilities": count, "configured": provider in configured_providers}
|
|
76
|
+
for provider, count in provider_capabilities.most_common()
|
|
77
|
+
],
|
|
78
|
+
"configured_providers": sorted(configured_providers),
|
|
79
|
+
"missing_providers": [
|
|
80
|
+
{"id": provider, "agents": count}
|
|
81
|
+
for provider, count in missing_provider_counts.most_common()
|
|
82
|
+
],
|
|
83
|
+
"items": items,
|
|
84
|
+
"source_summary": source_summary,
|
|
85
|
+
"next_steps": readiness_next_steps(missing_provider_counts),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def agent_readiness_item(
|
|
90
|
+
agent_id: str,
|
|
91
|
+
agent: dict[str, Any],
|
|
92
|
+
providers: Counter[str],
|
|
93
|
+
*,
|
|
94
|
+
configured_providers: set[str],
|
|
95
|
+
source_enabled_count: int,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
required = sorted(providers)
|
|
98
|
+
configured = sorted(provider for provider in required if provider in configured_providers)
|
|
99
|
+
missing = sorted(provider for provider in required if provider not in configured_providers)
|
|
100
|
+
status = "ready"
|
|
101
|
+
if missing and configured:
|
|
102
|
+
status = "partial"
|
|
103
|
+
elif missing:
|
|
104
|
+
status = "needs-setup"
|
|
105
|
+
return {
|
|
106
|
+
"id": agent_id,
|
|
107
|
+
"name": agent.get("name") or agent_id,
|
|
108
|
+
"status": status,
|
|
109
|
+
"required_providers": required,
|
|
110
|
+
"configured_providers": configured,
|
|
111
|
+
"missing_providers": missing,
|
|
112
|
+
"provider_capabilities": dict(sorted(providers.items())),
|
|
113
|
+
"source_enabled_capabilities": source_enabled_count,
|
|
114
|
+
"setup_commands": [f"agent provider configure {provider}" for provider in missing[:3]],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def configured_source_summary() -> dict[str, Any]:
|
|
119
|
+
sources = list_sources()
|
|
120
|
+
try:
|
|
121
|
+
status = source_status()
|
|
122
|
+
status_items = status.get("items") if isinstance(status.get("items"), list) else []
|
|
123
|
+
except Exception: # noqa: BLE001 - readiness must remain diagnostic, not fatal.
|
|
124
|
+
status = {"status": "missing", "items": []}
|
|
125
|
+
status_items = []
|
|
126
|
+
configured_providers = sorted(
|
|
127
|
+
{
|
|
128
|
+
str(item.get("provider"))
|
|
129
|
+
for item in status_items
|
|
130
|
+
if isinstance(item, dict) and item.get("status") == "ok" and item.get("provider")
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
providers_with_sources = sorted(
|
|
134
|
+
{
|
|
135
|
+
str(item.get("provider"))
|
|
136
|
+
for item in sources.get("items") or []
|
|
137
|
+
if isinstance(item, dict) and item.get("provider")
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
return {
|
|
141
|
+
"status": status.get("status"),
|
|
142
|
+
"sources_count": len(sources.get("items") or []),
|
|
143
|
+
"configured_providers": configured_providers,
|
|
144
|
+
"providers_with_sources": providers_with_sources,
|
|
145
|
+
"stored_secret": sources.get("stored_secret") is True or status.get("stored_secret") is True,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def readiness_next_steps(missing_provider_counts: Counter[str]) -> list[str]:
|
|
150
|
+
if not missing_provider_counts:
|
|
151
|
+
return []
|
|
152
|
+
return [f"agent provider configure {provider}" for provider, _ in missing_provider_counts.most_common(5)]
|