kushi-agents 6.4.0 → 6.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "6.4.0",
3
+ "version": "6.5.0",
4
4
  "description": "Install Kushi — multi-source project evidence agent with Comprehensive Structured Capture (CSC) into weekly-only files across Email, Teams, OneNote, Loop, SharePoint, Meetings, CRM, ADO. Meetings retain a sibling verbatim/ audit folder. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -234,8 +234,8 @@ async function interactiveSetup({ workspace, dryRun }) {
234
234
  function emit(obj) { process.stdout.write(JSON.stringify(obj) + '\n'); }
235
235
 
236
236
  const INTEGRATIONS_TEMPLATE = {
237
- crm: { instance: 'https://iscrm.crm.dynamics.com', table: 'incidents', request_id: null, record_id: null },
238
- ado: { organization: 'IndustrySolutions', project: 'IS Engagements', apiVersion: '7.1', engagement_id: null },
237
+ crm: { instance: 'https://iscrm.crm.dynamics.com', table: 'incidents', request_id: '<__FILL_ME_IN__>', record_id: '<__FILL_ME_IN__>' },
238
+ ado: { organization: 'IndustrySolutions', project: 'IS Engagements', apiVersion: '7.1', engagement_id: '<__FILL_ME_IN__>' },
239
239
  sharepoint: { allowed_tenants: [] },
240
240
  };
241
241
 
@@ -252,17 +252,26 @@ function applyRows(source, rows, currentBounds, currentInteg) {
252
252
  }
253
253
  if (source === 'meetings') {
254
254
  const existing = currentBounds.meetings?.joinUrls || [];
255
+ // v6.5.0: meeting boundaries MUST be real http(s) join URLs — pull-meetings
256
+ // can't resolve a subject string into a meeting at the WorkIQ layer (unlike
257
+ // teams chat topics). Reject anything that isn't a URL; track rejected
258
+ // subjects in `accepted` log via reason field for discover-report visibility.
259
+ const rejectedSubjects = [];
255
260
  const incoming = rows.map(r => {
256
261
  const url = r.join_url;
257
- if (url && !isPlaceholder(url) && isValidValueFor('meetings', 'join_url', url) && url.startsWith('http')) return url;
262
+ if (url && !isPlaceholder(url) && isValidValueFor('meetings', 'join_url', url) && /^https?:\/\//.test(url)) return url;
258
263
  const subj = r.subject;
259
- if (subj && !isPlaceholder(subj)) return subj;
264
+ if (subj && !isPlaceholder(subj)) rejectedSubjects.push(subj);
260
265
  return null;
261
266
  }).filter(Boolean);
262
267
  const merged = dedup([...existing, ...incoming]);
263
268
  const added = merged.filter(v => !existing.includes(v));
264
269
  if (added.length) accepted.push(...added);
265
- return { boundariesPatch: added.length ? { meetings: { joinUrls: merged } } : null, accepted };
270
+ return {
271
+ boundariesPatch: added.length ? { meetings: { joinUrls: merged } } : null,
272
+ accepted,
273
+ rejected: rejectedSubjects.length ? rejectedSubjects.map(s => ({ subject: s, reason: 'no-join-url' })) : undefined,
274
+ };
266
275
  }
267
276
  if (source === 'onenote') {
268
277
  const existing = currentBounds.onenote?.section_file_ids || [];
@@ -303,7 +312,7 @@ function applyRows(source, rows, currentBounds, currentInteg) {
303
312
  isValidValueFor('crm', 'request_id', r.request_id) ||
304
313
  isValidValueFor('crm', 'incident_number', r.incident_number)
305
314
  );
306
- if (!top) return { integrationsPatch: null, accepted: [] };
315
+ if (!top) return { integrationsPatch: null, accepted: [], unresolved: 'crm.request_id' };
307
316
  const id = isValidValueFor('crm', 'request_id', top.request_id) ? top.request_id : top.incident_number;
308
317
  const patch = { crm: { ...cur, request_id: id } };
309
318
  accepted.push(id);
@@ -318,7 +327,7 @@ function applyRows(source, rows, currentBounds, currentInteg) {
318
327
  isValidValueFor('ado', 'engagement_id', r.engagement_id) ||
319
328
  isValidValueFor('ado', 'work_item_id', r.work_item_id)
320
329
  );
321
- if (!top) return { integrationsPatch: null, accepted: [] };
330
+ if (!top) return { integrationsPatch: null, accepted: [], unresolved: 'ado.engagement_id' };
322
331
  const id = isValidValueFor('ado', 'engagement_id', top.engagement_id) ? top.engagement_id : top.work_item_id;
323
332
  const patch = { ado: { ...cur, engagement_id: id } };
324
333
  accepted.push(id);
@@ -448,7 +457,7 @@ async function main() {
448
457
  : `${skipReason} after ${elapsed}ms: ${(e.message || '').split('\n')[0].slice(0, 200)}`;
449
458
  log(` ${source}: ✗ ${detail}`);
450
459
  }
451
- const { boundariesPatch, integrationsPatch, accepted } = applyRows(source, rows, bounds, integ);
460
+ const { boundariesPatch, integrationsPatch, accepted, rejected, unresolved } = applyRows(source, rows, bounds, integ);
452
461
  if (boundariesPatch) {
453
462
  Object.assign(bounds, mergeShallow(bounds, boundariesPatch));
454
463
  boundsDirty = true;
@@ -457,7 +466,7 @@ async function main() {
457
466
  Object.assign(integ, mergeShallow(integ, integrationsPatch));
458
467
  integDirty = true;
459
468
  }
460
- sourceResults.push({ source, asked, found: rows.length, accepted, skipped_reason: skipReason });
469
+ sourceResults.push({ source, asked, found: rows.length, accepted, rejected, unresolved, skipped_reason: skipReason });
461
470
  }
462
471
 
463
472
  log(`done: ${sourceResults.filter(r => r.found > 0).length}/${total} sources returned data`);
@@ -73,13 +73,14 @@ function emit(obj) { process.stdout.write(JSON.stringify(obj) + '\n'); }
73
73
  */
74
74
  export function buildTargets(merged) {
75
75
  const targets = [];
76
+ const isPlaceholder = (v) => v == null || /^<.*>$/.test(String(v).trim()) || /^(unknown|n\/a|none|null|tbd|todo)$/i.test(String(v).trim());
76
77
  // crm: from integrations
77
78
  const crm = merged.crm || {};
78
79
  const crmEntity = crm.request_id || crm.record_id;
79
- if (crmEntity) targets.push({ source: 'crm', entity: String(crmEntity) });
80
+ if (crmEntity && !isPlaceholder(crmEntity)) targets.push({ source: 'crm', entity: String(crmEntity) });
80
81
  // ado
81
82
  const ado = merged.ado || {};
82
- if (ado.engagement_id) targets.push({ source: 'ado', entity: String(ado.engagement_id) });
83
+ if (ado.engagement_id && !isPlaceholder(ado.engagement_id)) targets.push({ source: 'ado', entity: String(ado.engagement_id) });
83
84
  // email: per-user mailbox folders
84
85
  const email = merged.email || {};
85
86
  for (const f of (email.folders || [])) {