create-agdf 0.13.7 → 0.14.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 +31 -7
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -4
- package/generated/plugins/agdf/hooks/copilot-hooks.json +15 -0
- package/generated/plugins/agdf/hooks/hooks.json +3 -2
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/plugin.json +22 -0
- package/generated/plugins/agdf/runtime/agdf-local.js +1 -1
- package/generated/plugins/agdf/runtime/agdf-session-check.js +97 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +10 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +73 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +50 -5
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -2
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +12 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
- package/lib/cli/application.js +291 -20
- package/lib/cli/command-registry.js +10 -2
- package/lib/cli/parse-args.js +17 -0
- package/lib/installers/copilot-precedence.js +34 -0
- package/lib/installers/copilot-settings.js +84 -0
- package/lib/installers/local-marketplace.js +2 -0
- package/lib/installers/opencode.js +21 -5
- package/lib/installers/plugin-installers.js +155 -4
- package/lib/lifecycle/operations.js +3 -0
- package/lib/lifecycle/presentation.js +43 -2
- package/lib/lifecycle/result.js +10 -0
- package/lib/lifecycle/status.js +7 -3
- package/lib/public-plugin/manifest.js +34 -2
- package/lib/public-plugin/validator.js +1 -1
- package/lib/release/version-coherence.js +1 -0
- package/lib/runtime-check-consent/adapters.js +42 -0
- package/lib/runtime-check-consent/claude-settings.js +71 -0
- package/lib/runtime-check-consent/contract.js +73 -0
- package/lib/runtime-check-consent/coordinator.js +38 -0
- package/lib/runtime-check-consent/service.js +181 -0
- package/lib/runtime-check-consent/state.js +90 -0
- package/lib/scaffold/plan.js +3 -89
- package/lib/scaffold/presentation.js +3 -22
- package/opencode-plugin.js +30 -15
- package/package.json +6 -3
- package/generated/.github/copilot-instructions.md +0 -16
- package/generated/.github/instructions/agdf-governance.instructions.md +0 -14
- package/generated/.github/skills/README.md +0 -20
- package/generated/AGENTS.md +0 -113
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-brownfield-analysis/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-clean-implementation-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-code-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-closeout/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-path-search/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-gate-check/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-interaction-locales.json +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-qa-gate/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-release-or/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-runtime-contract.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-task-plan-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-ux-intent-definition/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/closeout.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/context-graph.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/control-scaffold.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/gate-transition.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/interaction.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/modes.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/quality.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/task-target-resolution.md +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const OPERATIONS = Object.freeze([
|
|
4
|
+
"resolve-local-runtime",
|
|
5
|
+
"inspect-control-state",
|
|
6
|
+
"render-bounded-orientation",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function stable(value) {
|
|
10
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
11
|
+
if (!value || typeof value !== "object") return value;
|
|
12
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateRuntimeCheckCapability(capability) {
|
|
16
|
+
if (capability?.schemaVersion !== 1 || capability.capabilityId !== "automatic-runtime-checks") {
|
|
17
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: schema or capability id");
|
|
18
|
+
}
|
|
19
|
+
if (!Number.isInteger(capability.adapterContractVersion) || capability.adapterContractVersion < 1) {
|
|
20
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: adapter contract version");
|
|
21
|
+
}
|
|
22
|
+
if (capability.entrypoint !== "runtime/agdf-session-check.js") {
|
|
23
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: fixed entrypoint required");
|
|
24
|
+
}
|
|
25
|
+
if (JSON.stringify(capability.operations) !== JSON.stringify(OPERATIONS)) {
|
|
26
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: closed operation vocabulary required");
|
|
27
|
+
}
|
|
28
|
+
const constraints = capability.constraints ?? {};
|
|
29
|
+
if (constraints.arguments !== "forbidden" || constraints.filesystemWrites !== "forbidden"
|
|
30
|
+
|| constraints.network !== "forbidden" || constraints.gateAuthority !== "none") {
|
|
31
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: read-only constraints required");
|
|
32
|
+
}
|
|
33
|
+
const surfaces = capability.surfaces ?? {};
|
|
34
|
+
if (surfaces.codex !== "native-hook-review" || surfaces.claude !== "exact-command-rule"
|
|
35
|
+
|| surfaces.copilot !== "plugin-hook-review"
|
|
36
|
+
|| surfaces.opencode !== "plugin-internal"
|
|
37
|
+
|| surfaces["portable-skills"] !== "manual-external-required") {
|
|
38
|
+
throw new Error("AGDF_RUNTIME_CHECK_CAPABILITY_INVALID: surface contract");
|
|
39
|
+
}
|
|
40
|
+
return capability;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function runtimeCheckCapabilityIdentity({ capability, surface, runtimeDigest, sourceDigest, command }) {
|
|
44
|
+
validateRuntimeCheckCapability(capability);
|
|
45
|
+
if (!["codex", "claude", "copilot", "opencode"].includes(surface)) throw new Error("AGDF_RUNTIME_CHECK_SURFACE_INVALID");
|
|
46
|
+
for (const [name, value] of Object.entries({ runtimeDigest, sourceDigest })) {
|
|
47
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) throw new Error(`AGDF_RUNTIME_CHECK_IDENTITY_INVALID: ${name}`);
|
|
48
|
+
}
|
|
49
|
+
if (typeof command !== "string" || !command.trim()) throw new Error("AGDF_RUNTIME_CHECK_IDENTITY_INVALID: command");
|
|
50
|
+
const payload = stable({
|
|
51
|
+
schema_version: 1,
|
|
52
|
+
surface,
|
|
53
|
+
capability,
|
|
54
|
+
runtime_digest: runtimeDigest,
|
|
55
|
+
source_digest: sourceDigest,
|
|
56
|
+
command: command.trim(),
|
|
57
|
+
});
|
|
58
|
+
return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function fixedRuntimeCheckCommand(surface, pluginRoot, platform = process.platform) {
|
|
62
|
+
if (surface === "opencode") return "plugin:agdf:automatic-runtime-checks";
|
|
63
|
+
if (surface === "copilot") return 'node "${PLUGIN_ROOT}/runtime/agdf-session-check.js"';
|
|
64
|
+
if (surface === "codex" || surface === "claude") {
|
|
65
|
+
return platform === "win32"
|
|
66
|
+
? "node \"$([Environment]::GetEnvironmentVariable('PLUGIN_ROOT') + [Environment]::GetEnvironmentVariable('CLAUDE_PLUGIN_ROOT'))\\runtime\\agdf-session-check.js\""
|
|
67
|
+
: "node \"${PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/runtime/agdf-session-check.js\"";
|
|
68
|
+
}
|
|
69
|
+
const normalizedRoot = platform === "win32" ? pluginRoot.replaceAll("/", "\\") : pluginRoot;
|
|
70
|
+
return `node \"${normalizedRoot}${platform === "win32" ? "\\" : "/"}runtime${platform === "win32" ? "\\" : "/"}agdf-session-check.js\"`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const runtimeCheckOperations = OPERATIONS;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const RUNTIME_CHECK_DECISIONS = Object.freeze(["enable", "manual", "cancel"]);
|
|
2
|
+
|
|
3
|
+
export function resolveRuntimeCheckDecision({ explicitValue, interactive = false, ask }) {
|
|
4
|
+
if (explicitValue !== undefined) {
|
|
5
|
+
if (!RUNTIME_CHECK_DECISIONS.includes(explicitValue)) throw new Error("AGDF_RUNTIME_CHECK_DECISION_INVALID");
|
|
6
|
+
return explicitValue;
|
|
7
|
+
}
|
|
8
|
+
if (!interactive || typeof ask !== "function") return "manual";
|
|
9
|
+
const answer = ask({
|
|
10
|
+
question: "Allow this AGDF installation to run narrow local read-only checks automatically?",
|
|
11
|
+
options: [...RUNTIME_CHECK_DECISIONS],
|
|
12
|
+
default: null,
|
|
13
|
+
});
|
|
14
|
+
if (!RUNTIME_CHECK_DECISIONS.includes(answer)) return "cancel";
|
|
15
|
+
return answer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function consentDisclosure(surface) {
|
|
19
|
+
const permissionOwner = surface === "codex" ? "Codex native hook trust"
|
|
20
|
+
: surface === "claude" ? "Claude Code user permission settings"
|
|
21
|
+
: surface === "copilot" ? "GitHub Copilot plugin hook review"
|
|
22
|
+
: "OpenCode plugin and explicit permission configuration";
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
surface,
|
|
25
|
+
installation_scope: "global user installation",
|
|
26
|
+
runs: "one argument-free local session check",
|
|
27
|
+
when: "at session start",
|
|
28
|
+
executable: "runtime/agdf-session-check.js inside the installed AGDF runtime",
|
|
29
|
+
reads: "AGDF runtime identity and .agdf/control state in the current repository",
|
|
30
|
+
writes: "one AGDF-owned intent receipt and, only where supported, one exact host permission rule",
|
|
31
|
+
network: "none",
|
|
32
|
+
permission_owner: permissionOwner,
|
|
33
|
+
renewal: "material command, runtime, source, scope or adapter identity change requires renewed consent",
|
|
34
|
+
revocation: `npx --yes @agdf/cli@latest runtime-checks manual --surface ${surface}`,
|
|
35
|
+
gate_authority: "none",
|
|
36
|
+
choices: [...RUNTIME_CHECK_DECISIONS],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
7
|
+
import { defaultOpenCodeConfigDir, resolveOpenCodeInstalledPackage } from "../installers/opencode.js";
|
|
8
|
+
import { defaultAgdfDataRoot } from "../installers/local-marketplace.js";
|
|
9
|
+
import { digestNormalizedPluginSource } from "../runtime/plugin-provenance.js";
|
|
10
|
+
import { fixedRuntimeCheckCommand, runtimeCheckCapabilityIdentity } from "./contract.js";
|
|
11
|
+
import { consentDisclosure, resolveRuntimeCheckDecision } from "./coordinator.js";
|
|
12
|
+
import { claudePermissionRule } from "./adapters.js";
|
|
13
|
+
import { configureClaudeExactRuntimeRule, defaultClaudeSettingsPath, revokeClaudeRuntimeRule } from "./claude-settings.js";
|
|
14
|
+
import { createRuntimeCheckReceipt, readRuntimeCheckReceipt, writeRuntimeCheckReceipt } from "./state.js";
|
|
15
|
+
|
|
16
|
+
export { fixedRuntimeCheckCommand } from "./contract.js";
|
|
17
|
+
|
|
18
|
+
export function prepareInstallConsent(surface, options = {}, adapters = {}) {
|
|
19
|
+
const explicit = options.runtimeChecksDecision;
|
|
20
|
+
const interactive = explicit === undefined && Boolean(adapters.interactive);
|
|
21
|
+
const answer = interactive ? adapters.ask?.(consentDisclosure(surface)) : undefined;
|
|
22
|
+
const decision = resolveRuntimeCheckDecision({ explicitValue: explicit ?? answer, interactive: false });
|
|
23
|
+
return { decision, persist: explicit !== undefined || interactive, disclosure: consentDisclosure(surface) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function prospectiveRuntimeCheckIdentity(surface, platform = process.platform) {
|
|
27
|
+
if (surface === "opencode") {
|
|
28
|
+
const installed = resolveOpenCodeInstalledPackage(defaultOpenCodeConfigDir(), pluginDefinition.opencode.npmPackage);
|
|
29
|
+
if (!installed.loadable || !installed.digest) return null;
|
|
30
|
+
return runtimeCheckCapabilityIdentity({
|
|
31
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
32
|
+
surface,
|
|
33
|
+
runtimeDigest: installed.digest,
|
|
34
|
+
sourceDigest: installed.digest,
|
|
35
|
+
command: fixedRuntimeCheckCommand(surface, installed.root, platform),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!["codex", "claude", "copilot"].includes(surface)) return null;
|
|
39
|
+
const pluginRoot = join(generatedRoot, "plugins", "agdf");
|
|
40
|
+
let runtimeManifest;
|
|
41
|
+
try { runtimeManifest = JSON.parse(readFileSync(join(pluginRoot, "runtime", "runtime-manifest.json"), "utf8")); } catch { return null; }
|
|
42
|
+
if (!/^[a-f0-9]{64}$/.test(runtimeManifest?.digest ?? "")) return null;
|
|
43
|
+
const sourceDigest = digestNormalizedPluginSource(pluginRoot, pluginDefinition.version);
|
|
44
|
+
const command = fixedRuntimeCheckCommand(surface, pluginRoot, platform);
|
|
45
|
+
return runtimeCheckCapabilityIdentity({
|
|
46
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
47
|
+
surface,
|
|
48
|
+
runtimeDigest: runtimeManifest.digest,
|
|
49
|
+
sourceDigest,
|
|
50
|
+
command,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function retainCurrentInstallConsent(surface, dataRoot = defaultAgdfDataRoot(), platform = process.platform) {
|
|
55
|
+
const capabilityIdentity = prospectiveRuntimeCheckIdentity(surface, platform);
|
|
56
|
+
if (!capabilityIdentity) return null;
|
|
57
|
+
const current = readRuntimeCheckReceipt(dataRoot, surface);
|
|
58
|
+
if (current.status !== "valid" || current.receipt.capability_identity !== capabilityIdentity) return null;
|
|
59
|
+
return {
|
|
60
|
+
decision: current.receipt.requested_state === "enabled" ? "enable" : "manual",
|
|
61
|
+
persist: false,
|
|
62
|
+
retained: true,
|
|
63
|
+
disclosure: consentDisclosure(surface),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function persistInstallConsent({ surface, decision, installed, dataRoot, platform, claudeSettingsPath }) {
|
|
68
|
+
if (decision === "cancel") return { requested: "cancelled", effective: "cancelled", reason: "consent_not_provided", capability_identity: null, verification: "not_attempted", mutation: "none", rollback: "none" };
|
|
69
|
+
const runtimeDigest = installed?.runtimeDigest ?? installed?.digest;
|
|
70
|
+
if (!installed?.pluginRoot || !runtimeDigest || !installed?.sourceDigest) {
|
|
71
|
+
return { requested: decision === "enable" ? "enabled" : "manual", effective: "manual", reason: "host_permission_unverified", capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none" };
|
|
72
|
+
}
|
|
73
|
+
const command = fixedRuntimeCheckCommand(surface, installed.pluginRoot, platform);
|
|
74
|
+
const capabilityIdentity = runtimeCheckCapabilityIdentity({
|
|
75
|
+
capability: pluginDefinition.automaticRuntimeChecks,
|
|
76
|
+
surface,
|
|
77
|
+
runtimeDigest,
|
|
78
|
+
sourceDigest: installed.sourceDigest,
|
|
79
|
+
command,
|
|
80
|
+
});
|
|
81
|
+
const receipt = createRuntimeCheckReceipt({ surface, decision, capabilityIdentity, command });
|
|
82
|
+
let configured = null;
|
|
83
|
+
let configurationReason = null;
|
|
84
|
+
if (surface === "claude" && decision === "enable") {
|
|
85
|
+
configured = configureClaudeExactRuntimeRule({
|
|
86
|
+
path: claudeSettingsPath ?? defaultClaudeSettingsPath(),
|
|
87
|
+
rule: claudePermissionRule({ platform: platform ?? process.platform, command }),
|
|
88
|
+
});
|
|
89
|
+
if (configured.status !== "configured") {
|
|
90
|
+
configurationReason = configured.reason;
|
|
91
|
+
configured = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
writeRuntimeCheckReceipt(dataRoot ?? defaultAgdfDataRoot(), receipt);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
configured?.rollback();
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (configurationReason) return { requested: "enabled", effective: "degraded", reason: configurationReason, capability_identity: capabilityIdentity, verification: "conflict", mutation: "receipt_written", rollback: "none" };
|
|
101
|
+
return {
|
|
102
|
+
requested: receipt.requested_state,
|
|
103
|
+
effective: receipt.requested_state === "manual" ? "manual" : "decision_required",
|
|
104
|
+
reason: receipt.requested_state === "manual" ? "consent_not_provided" : "host_permission_unverified",
|
|
105
|
+
capability_identity: capabilityIdentity,
|
|
106
|
+
verification: receipt.requested_state === "manual" ? "not_required" : "host_unverified",
|
|
107
|
+
mutation: surface === "claude" && decision === "enable" ? "exact_rule_and_receipt_written" : "receipt_written",
|
|
108
|
+
rollback: "none",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function setRuntimeChecksManual({ dataRoot = defaultAgdfDataRoot(), surface, platform = process.platform, claudeSettingsPath }) {
|
|
113
|
+
const result = readRuntimeCheckReceipt(dataRoot, surface);
|
|
114
|
+
if (result.status === "receipt_missing") return { requested: "manual", effective: "manual", reason: "consent_not_provided", capability_identity: null, verification: "not_required", mutation: "none", rollback: "none", path: result.path };
|
|
115
|
+
if (result.status !== "valid") return { requested: "manual", effective: result.status, reason: result.status, capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none", path: result.path };
|
|
116
|
+
if (surface === "claude" && result.receipt.requested_state === "enabled") {
|
|
117
|
+
revokeClaudeRuntimeRule({
|
|
118
|
+
path: claudeSettingsPath ?? defaultClaudeSettingsPath(),
|
|
119
|
+
rule: claudePermissionRule({ platform, command: result.receipt.command }),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const receipt = createRuntimeCheckReceipt({
|
|
123
|
+
surface,
|
|
124
|
+
decision: "manual",
|
|
125
|
+
capabilityIdentity: result.receipt.capability_identity,
|
|
126
|
+
command: result.receipt.command,
|
|
127
|
+
});
|
|
128
|
+
writeRuntimeCheckReceipt(dataRoot, receipt);
|
|
129
|
+
return {
|
|
130
|
+
requested: "manual",
|
|
131
|
+
effective: "manual",
|
|
132
|
+
reason: "consent_not_provided",
|
|
133
|
+
capability_identity: receipt.capability_identity,
|
|
134
|
+
verification: "not_required",
|
|
135
|
+
mutation: surface === "claude" ? "exact_owned_rule_removed" : "receipt_updated",
|
|
136
|
+
rollback: "none",
|
|
137
|
+
path: result.path,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function runtimeCheckStatus(dataRoot = defaultAgdfDataRoot(), surface, platform = process.platform) {
|
|
142
|
+
const result = readRuntimeCheckReceipt(dataRoot, surface);
|
|
143
|
+
if (result.status !== "valid") return { requested: "unknown", effective: result.status, reason: result.status, capability_identity: null, verification: "unavailable", mutation: "none", rollback: "none", path: result.path };
|
|
144
|
+
const currentIdentity = prospectiveRuntimeCheckIdentity(surface, platform);
|
|
145
|
+
if (currentIdentity && currentIdentity !== result.receipt.capability_identity) {
|
|
146
|
+
return { requested: result.receipt.requested_state, effective: "renewal_required", reason: "capability_identity_changed", capability_identity: result.receipt.capability_identity, verification: "identity_mismatch", mutation: "none", rollback: "none", path: result.path };
|
|
147
|
+
}
|
|
148
|
+
return { requested: result.receipt.requested_state, effective: result.receipt.requested_state === "manual" ? "manual" : "decision_required", reason: result.receipt.requested_state === "manual" ? "consent_not_provided" : "host_permission_unverified", capability_identity: result.receipt.capability_identity, verification: result.receipt.requested_state === "manual" ? "not_required" : "host_unverified", mutation: "none", rollback: "none", path: result.path };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function executeOpenCodeAutomaticRuntimeCheck({
|
|
152
|
+
directory = process.cwd(),
|
|
153
|
+
dataRoot = defaultAgdfDataRoot(),
|
|
154
|
+
packageRoot = fileURLToPath(new URL("../../", import.meta.url)),
|
|
155
|
+
run = spawnSync,
|
|
156
|
+
executable = process.platform === "win32" ? "node.exe" : "node",
|
|
157
|
+
statusResolver = runtimeCheckStatus,
|
|
158
|
+
entrypointExists = existsSync,
|
|
159
|
+
} = {}) {
|
|
160
|
+
const status = statusResolver(dataRoot, "opencode");
|
|
161
|
+
if (status.requested !== "enabled" || status.effective !== "decision_required") {
|
|
162
|
+
return { ...status, ran: false, output: "" };
|
|
163
|
+
}
|
|
164
|
+
const entrypoint = join(packageRoot, "generated", "plugins", "agdf", "runtime", "agdf-session-check.js");
|
|
165
|
+
if (!entrypointExists(entrypoint)) {
|
|
166
|
+
return { ...status, effective: "unavailable", reason: "unsupported_host_capability", verification: "entrypoint_missing", ran: false, output: "" };
|
|
167
|
+
}
|
|
168
|
+
const child = run(executable, [entrypoint], {
|
|
169
|
+
cwd: directory,
|
|
170
|
+
env: { ...process.env, AGDF_SURFACE: "opencode" },
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
173
|
+
timeout: 10000,
|
|
174
|
+
maxBuffer: 1024 * 1024,
|
|
175
|
+
});
|
|
176
|
+
const output = String(child.stdout || "").trim().slice(0, 20000);
|
|
177
|
+
if (child.status !== 0 || !output.includes("AGDF automatic runtime check:")) {
|
|
178
|
+
return { ...status, effective: "degraded", reason: "host_permission_unverified", verification: "execution_failed", ran: true, output: "" };
|
|
179
|
+
}
|
|
180
|
+
return { ...status, effective: "enabled", reason: "none", verification: "host_observed", ran: true, output };
|
|
181
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
5
|
+
|
|
6
|
+
export function runtimeCheckReceiptPath(dataRoot, surface) {
|
|
7
|
+
if (!["codex", "claude", "copilot", "opencode"].includes(surface)) throw new Error("AGDF_RUNTIME_CHECK_RECEIPT_SURFACE_INVALID");
|
|
8
|
+
return join(resolve(dataRoot), "runtime-checks", `${surface}.json`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createRuntimeCheckReceipt({ surface, decision, capabilityIdentity, command }) {
|
|
12
|
+
if (!["codex", "claude", "copilot", "opencode"].includes(surface)) throw new Error("AGDF_RUNTIME_CHECK_RECEIPT_SURFACE_INVALID");
|
|
13
|
+
if (!["enable", "manual"].includes(decision)) throw new Error("AGDF_RUNTIME_CHECK_RECEIPT_DECISION_INVALID");
|
|
14
|
+
if (!/^[a-f0-9]{64}$/.test(capabilityIdentity)) throw new Error("AGDF_RUNTIME_CHECK_RECEIPT_IDENTITY_INVALID");
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
schema_version: 1,
|
|
17
|
+
owner: "create-agdf",
|
|
18
|
+
capability_id: "automatic-runtime-checks",
|
|
19
|
+
surface,
|
|
20
|
+
requested_state: decision === "enable" ? "enabled" : "manual",
|
|
21
|
+
capability_identity: capabilityIdentity,
|
|
22
|
+
command,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function validateRuntimeCheckReceipt(receipt) {
|
|
27
|
+
if (receipt?.schema_version !== 1 || receipt.owner !== "create-agdf"
|
|
28
|
+
|| receipt.capability_id !== "automatic-runtime-checks") return { status: "receipt_unowned", receipt: null };
|
|
29
|
+
if (!["codex", "claude", "copilot", "opencode"].includes(receipt.surface)
|
|
30
|
+
|| !["enabled", "manual"].includes(receipt.requested_state)
|
|
31
|
+
|| !/^[a-f0-9]{64}$/.test(receipt.capability_identity)
|
|
32
|
+
|| typeof receipt.command !== "string" || !receipt.command.trim()) {
|
|
33
|
+
return { status: "receipt_invalid", receipt: null };
|
|
34
|
+
}
|
|
35
|
+
return { status: "valid", receipt };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function readRuntimeCheckReceipt(dataRoot, surface) {
|
|
39
|
+
const path = runtimeCheckReceiptPath(dataRoot, surface);
|
|
40
|
+
if (!existsSync(path)) return { status: "receipt_missing", receipt: null, path };
|
|
41
|
+
try {
|
|
42
|
+
const file = lstatSync(path);
|
|
43
|
+
if (!file.isFile() || file.isSymbolicLink()) return { status: "receipt_unowned", receipt: null, path };
|
|
44
|
+
return { ...validateRuntimeCheckReceipt(JSON.parse(readFileSync(path, "utf8"))), path };
|
|
45
|
+
} catch {
|
|
46
|
+
return { status: "receipt_invalid", receipt: null, path };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeRuntimeCheckReceipt(dataRoot, receipt, adapters = {}) {
|
|
51
|
+
const validated = validateRuntimeCheckReceipt(receipt);
|
|
52
|
+
if (validated.status !== "valid") throw new Error(`AGDF_RUNTIME_CHECK_${validated.status.toUpperCase()}`);
|
|
53
|
+
const path = runtimeCheckReceiptPath(dataRoot, receipt.surface);
|
|
54
|
+
const parent = dirname(path);
|
|
55
|
+
const fs = {
|
|
56
|
+
mkdir: adapters.mkdir ?? mkdirSync,
|
|
57
|
+
write: adapters.write ?? writeFileSync,
|
|
58
|
+
rename: adapters.rename ?? renameSyncWithRetry,
|
|
59
|
+
remove: adapters.remove ?? rmSync,
|
|
60
|
+
};
|
|
61
|
+
fs.mkdir(parent, { recursive: true });
|
|
62
|
+
const parentStats = lstatSync(parent);
|
|
63
|
+
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) throw new Error("AGDF_RUNTIME_CHECK_RECEIPT_PARENT_UNOWNED");
|
|
64
|
+
const temp = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
65
|
+
const backup = `${path}.bak-${process.pid}-${randomUUID()}`;
|
|
66
|
+
const hadPrevious = existsSync(path);
|
|
67
|
+
fs.write(temp, `${JSON.stringify(receipt, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
68
|
+
try {
|
|
69
|
+
if (hadPrevious && process.platform === "win32") fs.rename(path, backup);
|
|
70
|
+
fs.rename(temp, path);
|
|
71
|
+
if (hadPrevious && process.platform === "win32") fs.remove(backup, { force: true });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
try { fs.remove(temp, { force: true }); } catch {}
|
|
74
|
+
if (hadPrevious && process.platform === "win32" && existsSync(backup) && !existsSync(path)) fs.rename(backup, path);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return path;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function deriveRuntimeCheckState({ receiptResult, surface, capabilityIdentity, hostEvidence }) {
|
|
81
|
+
if (receiptResult.status !== "valid") return { requested: "unknown", effective: receiptResult.status, reason: receiptResult.status };
|
|
82
|
+
const receipt = receiptResult.receipt;
|
|
83
|
+
if (receipt.surface !== surface) return { requested: receipt.requested_state, effective: "renewal_required", reason: "installed_identity_mismatch" };
|
|
84
|
+
if (receipt.capability_identity !== capabilityIdentity) return { requested: receipt.requested_state, effective: "renewal_required", reason: "capability_identity_changed" };
|
|
85
|
+
if (receipt.requested_state === "manual") return { requested: "manual", effective: "manual", reason: "consent_not_provided" };
|
|
86
|
+
if (hostEvidence?.status === "enabled" && hostEvidence.capability_identity === capabilityIdentity) {
|
|
87
|
+
return { requested: "enabled", effective: "enabled", reason: "none" };
|
|
88
|
+
}
|
|
89
|
+
return { requested: "enabled", effective: hostEvidence?.status || "decision_required", reason: hostEvidence?.reason || "host_permission_unverified" };
|
|
90
|
+
}
|
package/lib/scaffold/plan.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { generatedRoot, languageConfigContent, pluginDefinition } from "../cli/runtime-context.js";
|
|
4
4
|
export { doctorRequiredFiles } from "../control-evaluation/required-files.js";
|
|
5
5
|
|
|
6
|
-
export const agdfFragmentPath = "AGENTS.agdf.md";
|
|
7
6
|
const codexSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.codex.skillPrefix}${skill.slug}`);
|
|
8
|
-
const copilotSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.copilot.skillPrefix}${skill.slug}`);
|
|
9
7
|
const globalOpenCodeSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.opencode.globalSkillPrefix}${skill.slug}`);
|
|
10
8
|
const contractModules = [
|
|
11
9
|
"gate-transition.md",
|
|
@@ -96,63 +94,11 @@ const artefactTemplateFiles = [
|
|
|
96
94
|
join(".agdf", "control", "templates", "artefacts", "QA_REPORT.md"),
|
|
97
95
|
join(".agdf", "control", "templates", "artefacts", "OR.md"),
|
|
98
96
|
];
|
|
99
|
-
const copilotInstructionFiles = [
|
|
100
|
-
join(".github", "copilot-instructions.md"),
|
|
101
|
-
join(".github", "instructions", "agdf-governance.instructions.md"),
|
|
102
|
-
];
|
|
103
|
-
const copilotSkillFiles = [
|
|
104
|
-
join(".github", "skills", "README.md"),
|
|
105
|
-
join(".github", "skills", pluginDefinition.copilot.runtimeContractFileName),
|
|
106
|
-
...contractModules.map((moduleName) => join(".github", "skills", "contracts", moduleName)),
|
|
107
|
-
...copilotSkillNames.map((skillName) => join(".github", "skills", skillName, "SKILL.md")),
|
|
108
|
-
];
|
|
109
97
|
|
|
110
98
|
function loadAsset(relativePath) {
|
|
111
99
|
return readFileSync(join(generatedRoot, relativePath), "utf8");
|
|
112
100
|
}
|
|
113
101
|
|
|
114
|
-
function isAgdfOwnedAgentsFile(content) {
|
|
115
|
-
return content.includes("## Surface Convention")
|
|
116
|
-
&& content.includes("GitHub Copilot repository skills do not have a plugin namespace")
|
|
117
|
-
&& content.includes("AGDF is agent-native first and CLI-verifiable by design");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function addCopilotAgentsFile(files, targetDir, force) {
|
|
121
|
-
const agentsPath = join(targetDir, "AGENTS.md");
|
|
122
|
-
if (!existsSync(agentsPath) || force) {
|
|
123
|
-
files.push({
|
|
124
|
-
path: "AGENTS.md",
|
|
125
|
-
content: loadAsset("AGENTS.md"),
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const existingAgents = readFileSync(agentsPath, "utf8");
|
|
131
|
-
if (isAgdfOwnedAgentsFile(existingAgents)) {
|
|
132
|
-
files.push({
|
|
133
|
-
path: "AGENTS.md",
|
|
134
|
-
content: loadAsset("AGENTS.md"),
|
|
135
|
-
allowOverwrite: true,
|
|
136
|
-
action: "refreshed",
|
|
137
|
-
});
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
files.push({
|
|
142
|
-
path: agdfFragmentPath,
|
|
143
|
-
content: loadAsset("AGENTS.md"),
|
|
144
|
-
allowOverwrite: true,
|
|
145
|
-
action: "refreshed",
|
|
146
|
-
preserved: "AGENTS.md",
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function shouldWriteLanguageConfig(target, targetDir, force) {
|
|
151
|
-
if (force) return true;
|
|
152
|
-
if ((target === "copilot" || target === "both") && existsSync(join(targetDir, ".agdf", "control", "config.json"))) return false;
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
102
|
function addLanguageConfig(files, languagePreference) {
|
|
157
103
|
files.push({
|
|
158
104
|
path: join(".agdf", "control", "config.json"),
|
|
@@ -196,8 +142,8 @@ export function generatedFilesForTarget(target, targetDir, force, languagePrefer
|
|
|
196
142
|
return files;
|
|
197
143
|
}
|
|
198
144
|
|
|
199
|
-
if (target === "codex-repo"
|
|
200
|
-
|
|
145
|
+
if (target === "codex-repo") {
|
|
146
|
+
addLanguageConfig(files, languagePreference);
|
|
201
147
|
for (const codexPath of codexPluginFiles) {
|
|
202
148
|
files.push({
|
|
203
149
|
path: codexPath,
|
|
@@ -206,38 +152,6 @@ export function generatedFilesForTarget(target, targetDir, force, languagePrefer
|
|
|
206
152
|
}
|
|
207
153
|
}
|
|
208
154
|
|
|
209
|
-
if (target === "copilot" || target === "both") {
|
|
210
|
-
if (target !== "both" && shouldWriteLanguageConfig(target, targetDir, force)) addLanguageConfig(files, languagePreference);
|
|
211
|
-
addCopilotAgentsFile(files, targetDir, force);
|
|
212
|
-
|
|
213
|
-
for (const controlPath of controlFiles) {
|
|
214
|
-
files.push({
|
|
215
|
-
path: controlPath,
|
|
216
|
-
content: loadAsset(controlPath),
|
|
217
|
-
allowOverwrite: true,
|
|
218
|
-
action: "refreshed",
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
for (const instructionPath of copilotInstructionFiles) {
|
|
223
|
-
files.push({
|
|
224
|
-
path: instructionPath,
|
|
225
|
-
content: loadAsset(instructionPath),
|
|
226
|
-
allowOverwrite: true,
|
|
227
|
-
action: "refreshed",
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (const skillPath of copilotSkillFiles) {
|
|
232
|
-
files.push({
|
|
233
|
-
path: skillPath,
|
|
234
|
-
content: loadAsset(skillPath),
|
|
235
|
-
allowOverwrite: true,
|
|
236
|
-
action: "refreshed",
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
155
|
if (target === "opencode-repo") {
|
|
242
156
|
addLanguageConfig(files, languagePreference);
|
|
243
157
|
for (const controlPath of controlFiles) {
|
|
@@ -3,12 +3,9 @@ import { join } from "node:path";
|
|
|
3
3
|
import { pluginDefinition } from "../cli/runtime-context.js";
|
|
4
4
|
import { printLifecycleResult } from "../lifecycle/presentation.js";
|
|
5
5
|
import { createLifecycleResult } from "../lifecycle/result.js";
|
|
6
|
-
import { agdfFragmentPath } from "./plan.js";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function printNextSteps(target, destination, files, wroteAgentsFragment, removedOpenCodeAgents = [], { verbose = false, json = false, io = console } = {}) {
|
|
11
|
-
const repositorySurface = { "codex-repo": "codex", "opencode-repo": "opencode", copilot: "copilot" }[target];
|
|
7
|
+
export function printNextSteps(target, destination, files, removedOpenCodeAgents = [], { verbose = false, json = false, io = console } = {}) {
|
|
8
|
+
const repositorySurface = { "codex-repo": "codex", "opencode-repo": "opencode" }[target];
|
|
12
9
|
const verified = files.every((file) => {
|
|
13
10
|
const path = join(destination, file.path);
|
|
14
11
|
return existsSync(path) && readFileSync(path, "utf8") === file.content;
|
|
@@ -16,9 +13,7 @@ export function printNextSteps(target, destination, files, wroteAgentsFragment,
|
|
|
16
13
|
if (repositorySurface) {
|
|
17
14
|
const nextAction = repositorySurface === "codex"
|
|
18
15
|
? "Restart Codex, open /plugins, select This repository and install agdf; then start a new task with: Run an AGDF gate check for this request."
|
|
19
|
-
:
|
|
20
|
-
? "Restart OpenCode in this repository so the global AGDF plugin observes the durable control configuration."
|
|
21
|
-
: "Run /instructions in GitHub Copilot CLI to confirm that the generated AGDF repository instructions are visible.";
|
|
16
|
+
: "Restart OpenCode in this repository so the global AGDF plugin observes the durable control configuration.";
|
|
22
17
|
printLifecycleResult(createLifecycleResult({
|
|
23
18
|
operation: "repository_setup",
|
|
24
19
|
result: verified ? "success" : "partial",
|
|
@@ -79,25 +74,11 @@ export function printNextSteps(target, destination, files, wroteAgentsFragment,
|
|
|
79
74
|
io.log("- Run the installed `agdf doctor` when this repository also uses durable AGDF control state.");
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
82
|
-
if (wroteAgentsFragment) {
|
|
83
|
-
io.log(`- Existing AGENTS.md detected. Merge ${agdfFragmentPath} into your current AGENTS.md before using Copilot with AGDF.`);
|
|
84
|
-
}
|
|
85
|
-
if (target === "both") {
|
|
86
|
-
io.log("- Restart Codex in this repository, open /plugins, select This repository and install agdf.");
|
|
87
|
-
io.log("- Start a new Codex thread in this repository and ask: Run an AGDF gate check for this request.");
|
|
88
|
-
}
|
|
89
|
-
if (target === "both") {
|
|
90
|
-
io.log(`- Optional global Claude Code install: ${pluginInstallCommand}`);
|
|
91
|
-
}
|
|
92
77
|
if (target === "opencode-repo") {
|
|
93
78
|
io.log("- Install the global OpenCode surface once with npx --yes @agdf/cli@latest opencode if it is not already installed.");
|
|
94
79
|
io.log("- Start or restart OpenCode in this repository; valid .agdf/control/config.json activates the global AGDF runtime here.");
|
|
95
80
|
io.log("- Load agdf-global-gate-check through OpenCode's native skill tool for new build/change intent or unclear approval before later artefacts or implementation.");
|
|
96
81
|
io.log("- Existing .opencode files are left untouched as a compatibility path; this command does not copy a second runtime surface.");
|
|
97
82
|
}
|
|
98
|
-
if (target === "copilot" || target === "both") {
|
|
99
|
-
io.log("- In GitHub Copilot CLI, run /instructions after the AGENTS.md step is complete to confirm that AGDF instructions and the repository skills are visible.");
|
|
100
|
-
io.log("- Run npx --yes @agdf/cli@latest init when the repository needs live AGDF control files.");
|
|
101
|
-
}
|
|
102
83
|
io.log("- Commit the generated files so the repository becomes the source of truth.");
|
|
103
84
|
}
|
package/opencode-plugin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { evaluateOpenCodeRepositoryActivation } from "./lib/installers/opencode-activation.js";
|
|
3
|
+
import { executeOpenCodeAutomaticRuntimeCheck } from "./lib/runtime-check-consent/service.js";
|
|
3
4
|
|
|
4
5
|
const packageJson = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
5
6
|
|
|
@@ -15,7 +16,7 @@ const readValidatorExpectedVersion = () => {
|
|
|
15
16
|
}
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
export const AGDFPlugin = async ({ directory, client }) => {
|
|
19
|
+
export const AGDFPlugin = async ({ directory, client }, dependencies = {}) => {
|
|
19
20
|
const controlDir = `${directory}/.agdf/control`;
|
|
20
21
|
const activation = () => evaluateOpenCodeRepositoryActivation(directory);
|
|
21
22
|
const status = () => {
|
|
@@ -45,13 +46,11 @@ export const AGDFPlugin = async ({ directory, client }) => {
|
|
|
45
46
|
const appendGuidance = async (output, key) => {
|
|
46
47
|
if (!output || !Array.isArray(output[key])) {
|
|
47
48
|
try {
|
|
48
|
-
await
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
extra: { key, repositoryActivation: activation().state },
|
|
54
|
-
},
|
|
49
|
+
await safeLog({
|
|
50
|
+
service: "agdf",
|
|
51
|
+
level: "warn",
|
|
52
|
+
message: `AGDF OpenCode guidance degraded: ${key} output is unavailable`,
|
|
53
|
+
extra: { key, repositoryActivation: activation().state },
|
|
55
54
|
});
|
|
56
55
|
} catch {}
|
|
57
56
|
return;
|
|
@@ -65,17 +64,31 @@ export const AGDFPlugin = async ({ directory, client }) => {
|
|
|
65
64
|
} catch {}
|
|
66
65
|
};
|
|
67
66
|
|
|
67
|
+
const safeLog = async (body) => {
|
|
68
|
+
try {
|
|
69
|
+
const response = await client?.app?.log?.({ body });
|
|
70
|
+
if (response?.error) await client?.app?.log?.(body);
|
|
71
|
+
} catch {
|
|
72
|
+
try { await client?.app?.log?.(body); } catch {}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
let automaticCheck;
|
|
77
|
+
const currentAutomaticCheck = () => {
|
|
78
|
+
automaticCheck ??= (dependencies.executeAutomaticRuntimeCheck ?? executeOpenCodeAutomaticRuntimeCheck)({ directory });
|
|
79
|
+
return automaticCheck;
|
|
80
|
+
};
|
|
81
|
+
|
|
68
82
|
return {
|
|
69
83
|
event: async ({ event }) => {
|
|
70
84
|
if (event?.type === "session.created") {
|
|
71
85
|
const currentStatus = status();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
86
|
+
const runtimeCheck = currentAutomaticCheck();
|
|
87
|
+
await safeLog({
|
|
88
|
+
service: "agdf",
|
|
89
|
+
level: "info",
|
|
90
|
+
message: currentStatus.active ? "AGDF OpenCode active through durable control" : "AGDF OpenCode global hook active without durable control",
|
|
91
|
+
extra: { ...currentStatus, automatic_runtime_check: { effective: runtimeCheck.effective, reason: runtimeCheck.reason, ran: runtimeCheck.ran } },
|
|
79
92
|
});
|
|
80
93
|
if (!currentStatus.active) {
|
|
81
94
|
await safeToast(
|
|
@@ -112,6 +125,8 @@ export const AGDFPlugin = async ({ directory, client }) => {
|
|
|
112
125
|
|
|
113
126
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
114
127
|
await appendGuidance(output, "system");
|
|
128
|
+
const runtimeCheck = currentAutomaticCheck();
|
|
129
|
+
if (runtimeCheck.ran && runtimeCheck.output && Array.isArray(output?.system)) output.system.push(runtimeCheck.output);
|
|
115
130
|
},
|
|
116
131
|
|
|
117
132
|
"experimental.session.compacting": async (_input, output) => {
|