aienvmp 0.1.43 → 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 CHANGED
@@ -46,6 +46,12 @@ Short record of bugs, fixes, and follow-up checks.
46
46
  - Fix: `record` timeline entries now include follow-up metadata, and status/context/dashboard surface unresolved follow-ups.
47
47
  - Verification: Windows and macOS candidate smoke checks confirmed `record --target dependency` appears in `status --json` followUps and the dashboard Follow-ups card.
48
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
+
49
55
  ## Template
50
56
 
51
57
  ### Title
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 0.1.43
4
14
 
5
15
  - Added follow-up metadata to `record` timeline entries so dependency/security changes point agents back to sync, status, and handoff.
package/README.md CHANGED
@@ -51,6 +51,7 @@ AIENV.md # Markdown env map for AI agents
51
51
  - `status.json.nextAgent` tells the next AI what to read and whether to review first.
52
52
  - `dependencyReadSet` lists manifests and lockfiles before package or security changes.
53
53
  - `coordination.conflictTargets` shows where multiple agents are planning changes.
54
+ - `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
54
55
  - `followUps` shows records that still need `sync`, `status`, or `handoff`.
55
56
  - `handoff` carries dependency read-set and protocol guidance for the next AI.
56
57
  - Light SBOM includes source/confidence hints; verify security claims with dedicated scanners.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
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)) {
@@ -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/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", "followUps", "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;
@@ -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
@@ -15,6 +15,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
15
15
  const dependencyChangeProtocol = dependencyProtocol(manifest, dependencyReadSet);
16
16
  const coordination = coordinationSummary(intents);
17
17
  const followUps = pendingFollowUps(timeline);
18
+ const agentActivity = agentActivitySummary(timeline);
18
19
  return {
19
20
  schemaVersion: 1,
20
21
  contract: preflightContract(),
@@ -55,6 +56,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
55
56
  quickstart: agentQuickstart(decision.reviewRequired),
56
57
  nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
57
58
  coordination,
59
+ agentActivity,
58
60
  followUps,
59
61
  intentTargets,
60
62
  dependencyReadSet,
@@ -81,6 +83,42 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
81
83
  };
82
84
  }
83
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
+
84
122
  function coordinationSummary(intents = []) {
85
123
  const byTarget = new Map();
86
124
  for (const intent of intents) {
@@ -111,6 +149,41 @@ function coordinationSummary(intents = []) {
111
149
  };
112
150
  }
113
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
+
114
187
  function nextAgentHint(state, dependencyReadSet = [], dependencyChangeProtocol = {}) {
115
188
  const firstDependency = dependencyReadSet[0];
116
189
  const dependencyFiles = firstDependency
package/src/render.js CHANGED
@@ -176,6 +176,9 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
176
176
  "Follow-ups:",
177
177
  ...followUpLines(manifest.preflight?.followUps),
178
178
  "",
179
+ "Agent activity:",
180
+ ...agentActivityLines(manifest.preflight?.agentActivity),
181
+ "",
179
182
  "Open intents:",
180
183
  ...(intents.length ? intents.slice(-5).reverse().map((i) => `- ${i.actor}: ${i.action}`) : ["- none"]),
181
184
  "",
@@ -208,6 +211,9 @@ export function renderHandoff(handoff) {
208
211
  "Coordination:",
209
212
  ...coordinationHandoffLines(handoff.coordination),
210
213
  "",
214
+ "Agent activity:",
215
+ ...agentActivityLines(handoff.agentActivity),
216
+ "",
211
217
  "Warnings:",
212
218
  ...(handoff.warnings.length ? handoff.warnings.map((w) => `- ${w.message}`) : ["- none"]),
213
219
  "",
@@ -320,6 +326,16 @@ function followUpLines(followUps = []) {
320
326
  });
321
327
  }
322
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
+
323
339
  function environmentLines(item) {
324
340
  return [
325
341
  `- ${item.category}: ${item.summary}`,
@@ -448,6 +464,9 @@ const intentTargets=manifest.preflight?.intentTargets||[];
448
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>';
449
465
  const followUps=manifest.preflight?.followUps||[];
450
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>';
451
470
  const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
452
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>';
453
472
  const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
@@ -504,6 +523,8 @@ document.getElementById('app').innerHTML=\`
504
523
  <div style="height:14px"></div>
505
524
  \${card('Follow-ups',followUps.length?'<span class="pill warn">'+followUps.length+' pending</span>':'<span class="pill">clear</span>',followUpsHtml)}
506
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>
507
528
  \${card('AI Contract','<span class="pill">'+(contract.stability||'additive')+'</span>',contractHtml)}
508
529
  <div style="height:14px"></div>
509
530
  \${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}