kushi-agents 5.8.2 → 5.8.4

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": "5.8.2",
3
+ "version": "5.8.4",
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": {
@@ -89,16 +89,29 @@ function buildPrompt(source, projectName, scope = null) {
89
89
  ? scope.folders
90
90
  : ['Inbox']; // safe default — bounds the query so WorkIQ uses Graph filter, not mailbox-wide semantic search
91
91
  const isDefault = !(Array.isArray(scope.folders) && scope.folders.length > 0);
92
+ // matchingPolicy.mode drives whether we search by subfolder name, by mail content, or both.
93
+ // Recognized values: 'subfolder-only', 'keyword-only', 'hybrid' (default).
94
+ const mode = (scope.matchMode || 'hybrid').toLowerCase();
95
+ const doFolder = mode !== 'keyword-only';
96
+ const doKeyword = mode !== 'subfolder-only';
92
97
  lines.push('');
93
- if (scope.fuzzy !== false) {
94
- lines.push('Restrict your search to Outlook mail folders whose name CONTAINS any of these tokens (case-insensitive, fuzzy substring match — e.g. "FDE" matches "1. FDE", "01 FDE Active", "FDE-archive"):');
95
- } else {
96
- lines.push('Restrict your search to ONLY these Outlook mail folders (exact name match):');
97
- }
98
+ lines.push('SCOPE BOUNDARY — search ONLY within these Outlook parent folders and ALL nested descendants:');
98
99
  for (const f of folders) {
99
- lines.push(` • "${f}"${scope.includeSubfolders ? ' (and all subfolders)' : ''}`);
100
+ lines.push(` • "${f}"${scope.includeSubfolders !== false ? ' (recursively include every subfolder underneath)' : ' (this folder only)'}`);
101
+ }
102
+ lines.push('Parent folder name match: case-insensitive, fuzzy contains (so "1. FDE" matches "1. FDE", "01. FDE", "1 FDE").');
103
+ lines.push('Do NOT look outside this boundary.');
104
+ lines.push('');
105
+ lines.push(`SEARCH STRATEGY for project "${projectName}" (matchingPolicy.mode = "${mode}"):`);
106
+ if (doFolder && doKeyword) {
107
+ lines.push(` 1. Subfolder match: look for any subfolder whose name fuzzy-contains "${projectName}" (e.g. "${projectName}", "102. ${projectName}", "${projectName} - Engagement"). Emit each as a match.`);
108
+ lines.push(` 2. Mail-content match: also search email subjects, bodies, sender/recipient names within the boundary for "${projectName}" (case-insensitive). Group matching mail by folder and emit each folder.`);
109
+ lines.push(` Run BOTH and merge results — do not stop at step 1.`);
110
+ } else if (doFolder) {
111
+ lines.push(` Subfolder match only: emit subfolders whose name fuzzy-contains "${projectName}".`);
112
+ } else {
113
+ lines.push(` Mail-content match only: search email subjects, bodies, sender/recipient names within the boundary for "${projectName}" (case-insensitive). Group matching mail by folder and emit each folder.`);
100
114
  }
101
- lines.push('Do NOT scan any other mailbox folders.');
102
115
  if (isDefault) {
103
116
  lines.push('(Note: no project-specific folders configured — defaulting to Inbox+subfolders. For faster, more accurate results, populate emailContext.folders in m365-auth.json.)');
104
117
  }
@@ -59,7 +59,11 @@ export async function ask(prompt, { bin, timeoutMs = 120_000, env = process.env,
59
59
  err.code = 'WORKIQ_NOT_FOUND';
60
60
  throw err;
61
61
  }
62
- const { stdout, stderr, exitCode } = await runProcess(exe, ['ask', '-q', prompt], { timeoutMs, env, onHeartbeat, heartbeatMs });
62
+ // Pass prompt via stdin instead of `-q "<prompt>"` argument. Multi-line prompts
63
+ // with embedded quotes get mangled by cmd.exe's argument parser even with
64
+ // windowsVerbatimArguments, causing workiq to receive corrupted input and
65
+ // hang indefinitely. stdin is a clean byte channel.
66
+ const { stdout, stderr, exitCode } = await runProcess(exe, ['ask'], { timeoutMs, env, onHeartbeat, heartbeatMs, stdin: prompt });
63
67
  if (exitCode !== 0) {
64
68
  const err = new Error(`workiq exited ${exitCode}: ${stderr.slice(0, 1000)}`);
65
69
  err.code = 'WORKIQ_EXIT_NONZERO';
@@ -117,7 +121,7 @@ function parseKvLines(s) {
117
121
 
118
122
  async function pathExists(p) { try { await fs.access(p); return true; } catch { return false; } }
119
123
 
120
- function runProcess(exe, args, { timeoutMs, env, onHeartbeat = null, heartbeatMs = 10_000 }) {
124
+ function runProcess(exe, args, { timeoutMs, env, onHeartbeat = null, heartbeatMs = 10_000, stdin = null }) {
121
125
  return new Promise((resolve, reject) => {
122
126
  // Node 20.12+ refuses to spawn .cmd/.bat on Windows without shell:true
123
127
  // (CVE-2024-27980). Detect and route through cmd.exe explicitly with
@@ -133,6 +137,10 @@ function runProcess(exe, args, { timeoutMs, env, onHeartbeat = null, heartbeatMs
133
137
  spawnOpts.windowsVerbatimArguments = true;
134
138
  }
135
139
  const child = spawn(spawnExe, spawnArgs, spawnOpts);
140
+ if (stdin != null) {
141
+ try { child.stdin.write(stdin); child.stdin.end(); }
142
+ catch (e) { /* if stdin already closed, child error handler will catch */ }
143
+ }
136
144
  let stdout = '';
137
145
  let stderr = '';
138
146
  let stdoutBytes = 0;
@@ -41,6 +41,7 @@
41
41
  "sourceCoverageLabel": "",
42
42
  "matchingPolicy": {
43
43
  "mode": "hybrid",
44
+ "_mode_note": "How to find project-related mail within the SCOPE BOUNDARY (folders[] + dateFloor). One of: 'subfolder-only' (only match subfolder names containing the project name), 'keyword-only' (only search mail subjects/bodies for the project name), 'hybrid' (do both and merge — recommended default).",
44
45
  "rankingOrder": ["exact", "prefix", "contains"],
45
46
  "minConfidenceForFolderScopedSearch": "high",
46
47
  "fallbackToFullRootScanWhenAmbiguous": true,