aienvmp 0.1.42 → 0.1.44
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/BUGFIXES.md +12 -0
- package/CHANGELOG.md +19 -0
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/actions.js +4 -0
- package/src/commands/context.js +2 -1
- package/src/commands/dash.js +1 -1
- package/src/commands/handoff.js +2 -1
- package/src/commands/plan.js +3 -3
- package/src/commands/record.js +25 -0
- package/src/commands/status.js +3 -3
- package/src/contract.js +1 -1
- package/src/doctor.js +44 -0
- package/src/enforcement.js +1 -1
- package/src/preflight.js +77 -1
- package/src/render.js +36 -0
- package/src/timeline.js +23 -0
package/BUGFIXES.md
CHANGED
|
@@ -40,6 +40,18 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
40
40
|
- Fix: `doctor --json` now exposes `exitBehavior`, and enforcement gate metadata explains when strict mode sets a failure exit code.
|
|
41
41
|
- Verification: Windows and macOS candidate smoke checks confirmed default `doctor --json` exits successfully while `doctor --strict policy --json` fails on matching policy warnings.
|
|
42
42
|
|
|
43
|
+
### Record follow-up loop needed platform verification
|
|
44
|
+
|
|
45
|
+
- Issue: dependency/security records need to guide the next AI back through sync, status, and handoff without forcing operations.
|
|
46
|
+
- Fix: `record` timeline entries now include follow-up metadata, and status/context/dashboard surface unresolved follow-ups.
|
|
47
|
+
- Verification: Windows and macOS candidate smoke checks confirmed `record --target dependency` appears in `status --json` followUps and the dashboard Follow-ups card.
|
|
48
|
+
|
|
49
|
+
### Multi-agent records were less visible than open intents
|
|
50
|
+
|
|
51
|
+
- Issue: open intents showed planning conflicts, but two agents could record changes to the same env target after a handoff without a compact coordination signal.
|
|
52
|
+
- Fix: `doctor`, status/context, handoff, and the dashboard now expose same-target multi-agent records through advisory warnings and `agentActivity`.
|
|
53
|
+
- Verification: tests cover multi-agent record detection, later handoff reset, status JSON, handoff text, dashboard HTML, and recommended actions.
|
|
54
|
+
|
|
43
55
|
## Template
|
|
44
56
|
|
|
45
57
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.44
|
|
4
|
+
|
|
5
|
+
- Added multi-agent record warnings when multiple agents record environment changes for the same target after the last handoff.
|
|
6
|
+
- Added `agentActivity` to the shared preflight contract so status/context/handoff can expose recent env records by target and actor.
|
|
7
|
+
- Surfaced agent activity in context, handoff, and the dashboard for faster shared-server review.
|
|
8
|
+
- Added a recommended handoff action for multi-agent record activity.
|
|
9
|
+
- Mapped `multi-agent-records` into the optional `doctor --strict coordination` scope while keeping local behavior advisory by default.
|
|
10
|
+
- Documented the new AI contract field in the compact README.
|
|
11
|
+
- Added regression tests for warning detection, handoff reset behavior, status JSON, dashboard HTML, and recommended actions.
|
|
12
|
+
|
|
13
|
+
## 0.1.43
|
|
14
|
+
|
|
15
|
+
- Added follow-up metadata to `record` timeline entries so dependency/security changes point agents back to sync, status, and handoff.
|
|
16
|
+
- Added timeline follow-up summarization so unresolved dependency/security records can be surfaced consistently.
|
|
17
|
+
- Surfaced pending follow-ups in status/preflight and context outputs.
|
|
18
|
+
- Added a dashboard Follow-ups card for unresolved env/SBOM record refresh work.
|
|
19
|
+
- Updated the README change loop to show record, sync, status, and handoff as one simple continuation flow.
|
|
20
|
+
- Documented Windows/macOS candidate verification for the record follow-up loop.
|
|
21
|
+
|
|
3
22
|
## 0.1.42
|
|
4
23
|
|
|
5
24
|
- Added explicit enforcement gate metadata so AI and CI consumers know local checks are warn-only unless `--strict` or `--ci` is requested.
|
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ Before environment changes:
|
|
|
24
24
|
```bash
|
|
25
25
|
npx aienvmp intent --actor agent:codex --action "change dependency" --target dependency
|
|
26
26
|
npx aienvmp record --actor agent:codex --summary "dependency-change" --target dependency
|
|
27
|
+
npx aienvmp sync
|
|
28
|
+
npx aienvmp status --write
|
|
27
29
|
npx aienvmp handoff --record --actor agent:codex
|
|
28
30
|
```
|
|
29
31
|
|
|
@@ -49,6 +51,8 @@ AIENV.md # Markdown env map for AI agents
|
|
|
49
51
|
- `status.json.nextAgent` tells the next AI what to read and whether to review first.
|
|
50
52
|
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
51
53
|
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
54
|
+
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
55
|
+
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
|
52
56
|
- `handoff` carries dependency read-set and protocol guidance for the next AI.
|
|
53
57
|
- Light SBOM includes source/confidence hints; verify security claims with dedicated scanners.
|
|
54
58
|
- `enforcementProfile.gate` explains when checks warn, fail, and set exit codes.
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -15,6 +15,10 @@ export function recommendedActions(manifest = {}, context = {}) {
|
|
|
15
15
|
actions.push(action("coordinate-agents", "high", "coordination", "Multiple agents are planning changes to the same environment target. Coordinate with the user before proceeding."));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
if (warnings.some((warning) => warning.code === "multi-agent-records")) {
|
|
19
|
+
actions.push(action("review-agent-activity", "high", "coordination", "Multiple agents recorded changes for the same environment target. Review activity and record a handoff before more environment work.", "aienvmp handoff --record --actor agent:id"));
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
actions.push(...securityActions(manifest.security));
|
|
19
23
|
|
|
20
24
|
if (hasRuntimePolicyWarning(warnings)) {
|
package/src/commands/context.js
CHANGED
|
@@ -21,12 +21,13 @@ export async function contextWorkspace(args) {
|
|
|
21
21
|
const decision = aiDecision(warnings, intents);
|
|
22
22
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
23
23
|
const stepSummary = compactStepSummary(buildPlan(manifest, warnings, intents, policy));
|
|
24
|
-
const preflight = buildPreflight(manifest, warnings, intents);
|
|
24
|
+
const preflight = buildPreflight(manifest, warnings, intents, timeline);
|
|
25
25
|
if (args.json) {
|
|
26
26
|
console.log(JSON.stringify({
|
|
27
27
|
status: warnings.length ? "review-required" : "clear",
|
|
28
28
|
preflight,
|
|
29
29
|
coordination: preflight.coordination,
|
|
30
|
+
followUps: preflight.followUps,
|
|
30
31
|
decision,
|
|
31
32
|
enforcement: enforcementAdvice(warnings),
|
|
32
33
|
recommendedActions: actions,
|
package/src/commands/dash.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function dashWorkspace(args) {
|
|
|
24
24
|
const planEnvironment = await detectedPlanEnvironment(dir);
|
|
25
25
|
const html = renderDashboard({
|
|
26
26
|
...manifest,
|
|
27
|
-
preflight: buildPreflight(manifest, warnings, intents),
|
|
27
|
+
preflight: buildPreflight(manifest, warnings, intents, timeline),
|
|
28
28
|
recommendedActions: recommendedActions(manifest, { warnings, intents }),
|
|
29
29
|
planArtifacts,
|
|
30
30
|
planRemediation,
|
package/src/commands/handoff.js
CHANGED
|
@@ -46,7 +46,7 @@ async function recordHandoff(file, handoff, actor) {
|
|
|
46
46
|
export function buildHandoff(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
|
|
47
47
|
const reviewRequired = warnings.length > 0 || intents.length > 0;
|
|
48
48
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
49
|
-
const preflight = buildPreflight(manifest, warnings, intents);
|
|
49
|
+
const preflight = buildPreflight(manifest, warnings, intents, timeline);
|
|
50
50
|
return {
|
|
51
51
|
status: reviewRequired ? "review-required" : "clear",
|
|
52
52
|
trust: manifest.trust || {},
|
|
@@ -63,6 +63,7 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
63
63
|
security: securitySummary(manifest.security),
|
|
64
64
|
dependencyHandoff: dependencyHandoffSummary(preflight),
|
|
65
65
|
coordination: preflight.coordination,
|
|
66
|
+
agentActivity: preflight.agentActivity,
|
|
66
67
|
policy: {
|
|
67
68
|
node: policy.node || "not set",
|
|
68
69
|
python: policy.python || "not set",
|
package/src/commands/plan.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function planWorkspace(args) {
|
|
|
18
18
|
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
19
19
|
const policy = await loadPolicy(dir);
|
|
20
20
|
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
21
|
-
const plan = buildPlan(manifest, warnings, intents, policy);
|
|
21
|
+
const plan = buildPlan(manifest, warnings, intents, policy, timeline);
|
|
22
22
|
|
|
23
23
|
if (args.write) {
|
|
24
24
|
await writeJson(planJsonPath(dir), plan);
|
|
@@ -33,14 +33,14 @@ export async function planWorkspace(args) {
|
|
|
33
33
|
return plan;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
|
|
36
|
+
export function buildPlan(manifest, warnings = [], intents = [], policy = {}, timeline = []) {
|
|
37
37
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
38
38
|
const status = warnings.length || intents.length ? "review-required" : "clear";
|
|
39
39
|
return {
|
|
40
40
|
schemaVersion: 1,
|
|
41
41
|
generatedAt: new Date().toISOString(),
|
|
42
42
|
status,
|
|
43
|
-
preflight: buildPreflight(manifest, warnings, intents),
|
|
43
|
+
preflight: buildPreflight(manifest, warnings, intents, timeline),
|
|
44
44
|
workspace: manifest.workspace || {},
|
|
45
45
|
trust: manifest.trust || {},
|
|
46
46
|
decision: aiDecision(warnings, intents),
|
package/src/commands/record.js
CHANGED
|
@@ -18,12 +18,37 @@ export async function recordWorkspace(args) {
|
|
|
18
18
|
after: args.after || "",
|
|
19
19
|
evidence: args.evidence || "",
|
|
20
20
|
requiresReview,
|
|
21
|
+
followUp: followUpForRecord({ target: args.target, summary }),
|
|
21
22
|
trust: changedTrust(now, requiresReview)
|
|
22
23
|
};
|
|
23
24
|
await appendJsonLine(timelinePath(dir), entry);
|
|
24
25
|
console.log(`recorded ${entry.type} by ${actor}`);
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
export function followUpForRecord(record = {}) {
|
|
29
|
+
const target = String(record.target || "").toLowerCase();
|
|
30
|
+
const text = `${record.summary || ""} ${target}`.toLowerCase();
|
|
31
|
+
const isDependency = ["dependency", "package", "lockfile", "vulnerab", "security", "npm", "pnpm", "yarn", "pip", "uv"].some((item) => text.includes(item));
|
|
32
|
+
const isEnvironment = isDependency || ["node", "python", "docker", "runtime", "package-manager", "global"].some((item) => text.includes(item));
|
|
33
|
+
if (!isEnvironment) return {
|
|
34
|
+
required: false,
|
|
35
|
+
reason: "No environment follow-up detected.",
|
|
36
|
+
commands: []
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
required: true,
|
|
40
|
+
target: isDependency ? "dependency" : target || "environment",
|
|
41
|
+
reason: isDependency
|
|
42
|
+
? "Dependency or security records should refresh the env map and handoff context."
|
|
43
|
+
: "Environment records should refresh the env map and handoff context.",
|
|
44
|
+
commands: [
|
|
45
|
+
"aienvmp sync",
|
|
46
|
+
"aienvmp status --write",
|
|
47
|
+
"aienvmp handoff --record --actor agent:id"
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
function required(value, name) {
|
|
28
53
|
if (!value) throw new Error(`record: --${name} is required`);
|
|
29
54
|
return String(value);
|
package/src/commands/status.js
CHANGED
|
@@ -15,7 +15,7 @@ export async function statusWorkspace(args) {
|
|
|
15
15
|
const timeline = await readTimeline(timelinePath(dir));
|
|
16
16
|
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
17
17
|
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
18
|
-
const status = buildStatus(manifest, warnings, intents);
|
|
18
|
+
const status = buildStatus(manifest, warnings, intents, timeline);
|
|
19
19
|
const artifact = args.write ? await writeStatusArtifact(dir, status) : "";
|
|
20
20
|
const output = artifact ? { ...status, artifact } : status;
|
|
21
21
|
if (args.json) {
|
|
@@ -39,6 +39,6 @@ export async function writeStatusArtifact(dir, status) {
|
|
|
39
39
|
return out;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function buildStatus(manifest = {}, warnings = [], intents = []) {
|
|
43
|
-
return buildPreflight(manifest, warnings, intents);
|
|
42
|
+
export function buildStatus(manifest = {}, warnings = [], intents = [], timeline = []) {
|
|
43
|
+
return buildPreflight(manifest, warnings, intents, timeline);
|
|
44
44
|
}
|
package/src/contract.js
CHANGED
|
@@ -4,7 +4,7 @@ export function preflightContract() {
|
|
|
4
4
|
version: 1,
|
|
5
5
|
stability: "additive",
|
|
6
6
|
requiredFields: ["schemaVersion", "state", "decision", "quickstart", "commands", "artifacts"],
|
|
7
|
-
aiEntryFields: ["state", "summary", "nextAgent", "coordination", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
7
|
+
aiEntryFields: ["state", "summary", "nextAgent", "coordination", "agentActivity", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
8
8
|
rule: "Consumers should ignore unknown fields and treat missing optional fields as unavailable."
|
|
9
9
|
};
|
|
10
10
|
}
|
package/src/doctor.js
CHANGED
|
@@ -46,6 +46,7 @@ export function diagnose(manifest, context = {}) {
|
|
|
46
46
|
warnings.push(...coordinationWarnings(context.intents || []));
|
|
47
47
|
warnings.push(...staleIntentWarnings(context.intents || []));
|
|
48
48
|
warnings.push(...handoffWarnings(context.timeline || []));
|
|
49
|
+
warnings.push(...multiAgentRecordWarnings(context.timeline || []));
|
|
49
50
|
return warnings;
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -108,6 +109,32 @@ export function handoffWarnings(timeline = []) {
|
|
|
108
109
|
}];
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
export function multiAgentRecordWarnings(timeline = []) {
|
|
113
|
+
const lastHandoffAt = lastTime(timeline, (item) => item.type === "agent-handoff");
|
|
114
|
+
const byTarget = new Map();
|
|
115
|
+
for (const item of timeline) {
|
|
116
|
+
if (!isEnvironmentChange(item)) continue;
|
|
117
|
+
if (lastHandoffAt && timeOf(item) <= lastHandoffAt) continue;
|
|
118
|
+
const actor = String(item.actor || "").trim();
|
|
119
|
+
if (!actor) continue;
|
|
120
|
+
const target = normalizeTarget(item.target || inferTarget(`${item.summary || ""} ${item.action || ""} ${item.change?.key || ""}`) || "environment");
|
|
121
|
+
const list = byTarget.get(target) || [];
|
|
122
|
+
list.push(item);
|
|
123
|
+
byTarget.set(target, list);
|
|
124
|
+
}
|
|
125
|
+
const warnings = [];
|
|
126
|
+
for (const [target, list] of byTarget) {
|
|
127
|
+
const actors = [...new Set(list.map((item) => item.actor).filter(Boolean))];
|
|
128
|
+
if (actors.length < 2) continue;
|
|
129
|
+
warnings.push({
|
|
130
|
+
code: "multi-agent-records",
|
|
131
|
+
target,
|
|
132
|
+
message: `Multiple agents recorded environment changes for ${target} after the last handoff. Run handoff and review follow-ups before continuing.`
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return warnings;
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
function inferTarget(action = "") {
|
|
112
139
|
const normalized = String(action).toLowerCase();
|
|
113
140
|
for (const target of ["dependency", "node", "python", "docker", "npm", "pnpm", "yarn", "uv", "pip", "pipx"]) {
|
|
@@ -117,6 +144,23 @@ function inferTarget(action = "") {
|
|
|
117
144
|
return "";
|
|
118
145
|
}
|
|
119
146
|
|
|
147
|
+
function normalizeTarget(target = "") {
|
|
148
|
+
const normalized = String(target).trim().toLowerCase();
|
|
149
|
+
if (["npm", "pnpm", "yarn"].includes(normalized)) return "package-manager";
|
|
150
|
+
if (["pip", "pipx", "uv"].includes(normalized)) return "python";
|
|
151
|
+
return normalized || "environment";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function lastTime(items = [], predicate) {
|
|
155
|
+
const item = [...items].reverse().find(predicate);
|
|
156
|
+
return item ? timeOf(item) : 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function timeOf(item = {}) {
|
|
160
|
+
const value = new Date(item.at || 0).getTime();
|
|
161
|
+
return Number.isFinite(value) ? value : 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
120
164
|
function isEnvironmentChange(item = {}) {
|
|
121
165
|
if (["runtime", "package-manager", "container", "dependency"].includes(item.change?.scope)) return true;
|
|
122
166
|
if (item.type === "detected-change") return false;
|
package/src/enforcement.js
CHANGED
|
@@ -67,6 +67,6 @@ function warningMatchesScope(warning, scope) {
|
|
|
67
67
|
|
|
68
68
|
function warningScope(code = "") {
|
|
69
69
|
if (code === "security-vulnerabilities") return "security";
|
|
70
|
-
if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
|
|
70
|
+
if (["conflicting-open-intents", "stale-open-intent", "handoff-stale", "multi-agent-records"].includes(code)) return "coordination";
|
|
71
71
|
return "policy";
|
|
72
72
|
}
|
package/src/preflight.js
CHANGED
|
@@ -2,8 +2,9 @@ import { recommendedActions } from "./actions.js";
|
|
|
2
2
|
import { aiDecision } from "./decision.js";
|
|
3
3
|
import { enforcementAdvice, enforcementGate } from "./enforcement.js";
|
|
4
4
|
import { preflightContract } from "./contract.js";
|
|
5
|
+
import { pendingFollowUps } from "./timeline.js";
|
|
5
6
|
|
|
6
|
-
export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
7
|
+
export function buildPreflight(manifest = {}, warnings = [], intents = [], timeline = []) {
|
|
7
8
|
const decision = aiDecision(warnings, intents);
|
|
8
9
|
const enforcement = enforcementAdvice(warnings);
|
|
9
10
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
@@ -13,6 +14,8 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
13
14
|
const dependencyReadSet = dependencyPreflightReadSet(manifest);
|
|
14
15
|
const dependencyChangeProtocol = dependencyProtocol(manifest, dependencyReadSet);
|
|
15
16
|
const coordination = coordinationSummary(intents);
|
|
17
|
+
const followUps = pendingFollowUps(timeline);
|
|
18
|
+
const agentActivity = agentActivitySummary(timeline);
|
|
16
19
|
return {
|
|
17
20
|
schemaVersion: 1,
|
|
18
21
|
contract: preflightContract(),
|
|
@@ -53,6 +56,8 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
53
56
|
quickstart: agentQuickstart(decision.reviewRequired),
|
|
54
57
|
nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
|
|
55
58
|
coordination,
|
|
59
|
+
agentActivity,
|
|
60
|
+
followUps,
|
|
56
61
|
intentTargets,
|
|
57
62
|
dependencyReadSet,
|
|
58
63
|
dependencyChangeProtocol,
|
|
@@ -78,6 +83,42 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
function agentActivitySummary(timeline = []) {
|
|
87
|
+
const lastHandoffAt = lastTime(timeline, (item) => item.type === "agent-handoff");
|
|
88
|
+
const envRecords = timeline
|
|
89
|
+
.filter((item) => isEnvironmentRecord(item))
|
|
90
|
+
.filter((item) => !lastHandoffAt || timeOf(item) > lastHandoffAt);
|
|
91
|
+
const byTarget = new Map();
|
|
92
|
+
for (const item of envRecords) {
|
|
93
|
+
const target = normalizeTarget(item.target || targetFromText(`${item.summary || ""} ${item.action || ""}`) || "environment") || "environment";
|
|
94
|
+
const summary = byTarget.get(target) || { target, count: 0, actors: [], latest: null, multiActor: false };
|
|
95
|
+
summary.count += 1;
|
|
96
|
+
if (item.actor && !summary.actors.includes(item.actor)) summary.actors.push(item.actor);
|
|
97
|
+
if (!summary.latest || timeOf(item) > timeOf(summary.latest)) summary.latest = item;
|
|
98
|
+
byTarget.set(target, summary);
|
|
99
|
+
}
|
|
100
|
+
const targets = [...byTarget.values()].map((item) => ({
|
|
101
|
+
target: item.target,
|
|
102
|
+
count: item.count,
|
|
103
|
+
actors: item.actors.slice(0, 5),
|
|
104
|
+
latestAt: item.latest?.at || "",
|
|
105
|
+
latestSummary: item.latest?.summary || item.latest?.action || item.latest?.type || "",
|
|
106
|
+
multiActor: item.actors.length > 1
|
|
107
|
+
}));
|
|
108
|
+
const multiActorTargets = targets.filter((item) => item.multiActor).map((item) => item.target);
|
|
109
|
+
return {
|
|
110
|
+
sinceLastHandoff: lastHandoffAt ? "after-last-handoff" : "all-recorded",
|
|
111
|
+
environmentRecordCount: envRecords.length,
|
|
112
|
+
targets,
|
|
113
|
+
multiActorTargets,
|
|
114
|
+
next: multiActorTargets.length
|
|
115
|
+
? "Run handoff and review follow-ups before another environment change."
|
|
116
|
+
: envRecords.length
|
|
117
|
+
? "Run handoff before another AI continues environment work."
|
|
118
|
+
: "No environment records need coordination."
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
81
122
|
function coordinationSummary(intents = []) {
|
|
82
123
|
const byTarget = new Map();
|
|
83
124
|
for (const intent of intents) {
|
|
@@ -108,6 +149,41 @@ function coordinationSummary(intents = []) {
|
|
|
108
149
|
};
|
|
109
150
|
}
|
|
110
151
|
|
|
152
|
+
function isEnvironmentRecord(item = {}) {
|
|
153
|
+
if (item.type === "detected-change") return false;
|
|
154
|
+
const text = `${item.type || ""} ${item.target || ""} ${item.summary || ""} ${item.action || ""} ${item.change?.scope || ""} ${item.change?.key || ""}`.toLowerCase();
|
|
155
|
+
return [
|
|
156
|
+
"dependency",
|
|
157
|
+
"dependencies",
|
|
158
|
+
"package",
|
|
159
|
+
"lockfile",
|
|
160
|
+
"vulnerability",
|
|
161
|
+
"runtime",
|
|
162
|
+
"node",
|
|
163
|
+
"python",
|
|
164
|
+
"docker",
|
|
165
|
+
"package manager",
|
|
166
|
+
"package-manager",
|
|
167
|
+
"npm",
|
|
168
|
+
"pnpm",
|
|
169
|
+
"yarn",
|
|
170
|
+
"uv",
|
|
171
|
+
"pip",
|
|
172
|
+
"pipx",
|
|
173
|
+
"global"
|
|
174
|
+
].some((token) => text.includes(token));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function lastTime(items = [], predicate) {
|
|
178
|
+
const item = [...items].reverse().find(predicate);
|
|
179
|
+
return item ? timeOf(item) : 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function timeOf(item = {}) {
|
|
183
|
+
const value = new Date(item.at || 0).getTime();
|
|
184
|
+
return Number.isFinite(value) ? value : 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
111
187
|
function nextAgentHint(state, dependencyReadSet = [], dependencyChangeProtocol = {}) {
|
|
112
188
|
const firstDependency = dependencyReadSet[0];
|
|
113
189
|
const dependencyFiles = firstDependency
|
package/src/render.js
CHANGED
|
@@ -173,6 +173,12 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
|
|
|
173
173
|
"Enforcement gate:",
|
|
174
174
|
...enforcementGateLines(manifest.preflight?.enforcementProfile?.gate),
|
|
175
175
|
"",
|
|
176
|
+
"Follow-ups:",
|
|
177
|
+
...followUpLines(manifest.preflight?.followUps),
|
|
178
|
+
"",
|
|
179
|
+
"Agent activity:",
|
|
180
|
+
...agentActivityLines(manifest.preflight?.agentActivity),
|
|
181
|
+
"",
|
|
176
182
|
"Open intents:",
|
|
177
183
|
...(intents.length ? intents.slice(-5).reverse().map((i) => `- ${i.actor}: ${i.action}`) : ["- none"]),
|
|
178
184
|
"",
|
|
@@ -205,6 +211,9 @@ export function renderHandoff(handoff) {
|
|
|
205
211
|
"Coordination:",
|
|
206
212
|
...coordinationHandoffLines(handoff.coordination),
|
|
207
213
|
"",
|
|
214
|
+
"Agent activity:",
|
|
215
|
+
...agentActivityLines(handoff.agentActivity),
|
|
216
|
+
"",
|
|
208
217
|
"Warnings:",
|
|
209
218
|
...(handoff.warnings.length ? handoff.warnings.map((w) => `- ${w.message}`) : ["- none"]),
|
|
210
219
|
"",
|
|
@@ -309,6 +318,24 @@ function enforcementGateLines(gate = {}) {
|
|
|
309
318
|
];
|
|
310
319
|
}
|
|
311
320
|
|
|
321
|
+
function followUpLines(followUps = []) {
|
|
322
|
+
if (!followUps.length) return ["- none"];
|
|
323
|
+
return followUps.slice(0, 5).map((item) => {
|
|
324
|
+
const command = item.commands?.[0] ? ` (${item.commands[0]})` : "";
|
|
325
|
+
return `- ${item.target || "environment"}: ${item.summary || item.reason || "follow-up required"}${command}`;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function agentActivityLines(activity = {}) {
|
|
330
|
+
const targets = activity.targets || [];
|
|
331
|
+
if (!targets.length) return ["- none"];
|
|
332
|
+
return targets.slice(0, 5).map((item) => {
|
|
333
|
+
const actors = item.actors?.length ? item.actors.join(", ") : "unknown";
|
|
334
|
+
const flag = item.multiActor ? "multi-agent" : "single-agent";
|
|
335
|
+
return `- ${item.target}: ${item.count} record(s), ${actors}, ${flag}${item.latestSummary ? ` - ${item.latestSummary}` : ""}`;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
312
339
|
function environmentLines(item) {
|
|
313
340
|
return [
|
|
314
341
|
`- ${item.category}: ${item.summary}`,
|
|
@@ -435,6 +462,11 @@ const contract=manifest.preflight?.contract||{};
|
|
|
435
462
|
const contractHtml=contract.name?\`<table><tr><th>Name</th><td><code>\${esc(contract.name)}</code></td></tr><tr><th>Version</th><td><code>\${esc(contract.version||1)}</code></td></tr><tr><th>Stability</th><td><code>\${esc(contract.stability||'additive')}</code></td></tr><tr><th>AI fields</th><td>\${esc((contract.aiEntryFields||[]).join(', ')||'none')}</td></tr></table><div class="path">\${esc(contract.rule||'Ignore unknown fields.')}</div>\`:'<div class="okline">Run <code>aienvmp status --write</code> to include AI contract metadata.</div>';
|
|
436
463
|
const intentTargets=manifest.preflight?.intentTargets||[];
|
|
437
464
|
const intentTargetsHtml=intentTargets.length?'<div class="timeline">'+intentTargets.slice(0,5).map(t=>\`<div class="event"><time>\${esc(t.target)}</time><div><b>\${esc(t.target)}</b> \${esc(t.reason||'Record this target before environment changes.')}\${t.command?\`<div class="path">\${esc(t.command)}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No specific target recommendation. Use <code>aienvmp intent --actor agent:id --action planned-change</code>.</div>';
|
|
465
|
+
const followUps=manifest.preflight?.followUps||[];
|
|
466
|
+
const followUpsHtml=followUps.length?'<div class="timeline">'+followUps.slice(0,5).map(f=>\`<div class="event"><time>\${esc(f.target||'env')}</time><div><b>\${esc(f.summary||'follow-up')}</b> \${esc(f.reason||'Refresh shared context.')}\${f.commands?.length?\`<div class="path">\${esc(f.commands.join(' -> '))}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No pending follow-ups after environment records.</div>';
|
|
467
|
+
const agentActivity=manifest.preflight?.agentActivity||{};
|
|
468
|
+
const activityTargets=agentActivity.targets||[];
|
|
469
|
+
const activityHtml=activityTargets.length?'<div class="timeline">'+activityTargets.slice(0,5).map(a=>\`<div class="event"><time>\${esc(a.target||'env')}</time><div><b>\${esc((a.actors||[]).join(', ')||'unknown')}</b> \${esc(a.count||0)} record(s) \${a.multiActor?'<code>multi-agent</code>':'<code>single-agent</code>'}\${a.latestSummary?\`<div class="path">\${esc(a.latestSummary)}</div>\`:''}</div></div>\`).join('')+'</div><div class="path">'+esc(agentActivity.next||'Review activity before environment changes.')+'</div>':'<div class="okline">No recorded environment activity needs coordination.</div>';
|
|
438
470
|
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
439
471
|
const dependencyReadSetHtml=dependencyReadSet.length?'<div class="timeline">'+dependencyReadSet.slice(0,5).map(d=>\`<div class="event"><time>\${esc(d.ecosystem||'deps')}</time><div><b>\${esc(d.manifest||'dependency files')}</b> <code>\${esc(d.manager||'unknown')}</code><div class="path">\${esc([d.manifest,...(d.lockfiles||[])].filter(Boolean).join(', '))}</div>\${d.riskPackages?.length?\`<div class="path">risk: \${esc(d.riskPackages.join(', '))}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No dependency files detected.</div>';
|
|
440
472
|
const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
|
|
@@ -489,6 +521,10 @@ document.getElementById('app').innerHTML=\`
|
|
|
489
521
|
<div style="height:14px"></div>
|
|
490
522
|
\${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
|
|
491
523
|
<div style="height:14px"></div>
|
|
524
|
+
\${card('Follow-ups',followUps.length?'<span class="pill warn">'+followUps.length+' pending</span>':'<span class="pill">clear</span>',followUpsHtml)}
|
|
525
|
+
<div style="height:14px"></div>
|
|
526
|
+
\${card('Agent Activity',agentActivity.multiActorTargets?.length?'<span class="pill warn">'+agentActivity.multiActorTargets.length+' shared</span>':'<span class="pill">clear</span>',activityHtml)}
|
|
527
|
+
<div style="height:14px"></div>
|
|
492
528
|
\${card('AI Contract','<span class="pill">'+(contract.stability||'additive')+'</span>',contractHtml)}
|
|
493
529
|
<div style="height:14px"></div>
|
|
494
530
|
\${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}
|
package/src/timeline.js
CHANGED
|
@@ -44,3 +44,26 @@ export function newIntentID(now = new Date()) {
|
|
|
44
44
|
const entropy = Math.random().toString(36).slice(2, 8);
|
|
45
45
|
return `int_${time}_${entropy}`;
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
export function pendingFollowUps(timeline = []) {
|
|
49
|
+
const lastSync = [...timeline].reverse().find((item) => item.type === "sync" || item.type === "detected-change");
|
|
50
|
+
const lastHandoff = [...timeline].reverse().find((item) => item.type === "agent-handoff");
|
|
51
|
+
const lastSyncAt = lastSync ? new Date(lastSync.at).getTime() : 0;
|
|
52
|
+
const lastHandoffAt = lastHandoff ? new Date(lastHandoff.at).getTime() : 0;
|
|
53
|
+
return timeline
|
|
54
|
+
.filter((item) => item.followUp?.required)
|
|
55
|
+
.filter((item) => {
|
|
56
|
+
const at = new Date(item.at).getTime();
|
|
57
|
+
return at > lastSyncAt || at > lastHandoffAt;
|
|
58
|
+
})
|
|
59
|
+
.slice(-5)
|
|
60
|
+
.reverse()
|
|
61
|
+
.map((item) => ({
|
|
62
|
+
at: item.at,
|
|
63
|
+
actor: item.actor || "unknown",
|
|
64
|
+
target: item.followUp.target || item.target || "environment",
|
|
65
|
+
summary: item.summary || item.type || "environment record",
|
|
66
|
+
reason: item.followUp.reason || "Follow-up is required.",
|
|
67
|
+
commands: item.followUp.commands || []
|
|
68
|
+
}));
|
|
69
|
+
}
|