kushi-agents 5.7.3 → 5.7.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.7.
|
|
3
|
+
"version": "5.7.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": {
|
|
@@ -4,6 +4,35 @@ Newest on top. Format defined in [`README.md`](./README.md). Use this file when
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
### 2026-05-29 — Silent runners get killed by host watchdogs (GitHub Copilot Chat)
|
|
8
|
+
|
|
9
|
+
**Symptom**: User invoked `discover.mjs` from inside GitHub Copilot Chat in VS Code. The runner appeared to hang for 60+ seconds, then the host announced "discover did not return completion output (it stayed silent/hung)" and gave up. Direct PowerShell invocation outside the host showed the same long pause but eventually returned.
|
|
10
|
+
|
|
11
|
+
**Root cause**: `discover.mjs` runs 7 sequential WorkIQ calls (email, teams, meetings, onenote, sharepoint, crm, ado). Each call is 5–60s. The runner emitted **zero output** during the loop and only printed the JSON envelope at the end, so total silent time was up to 7 × 60s = 7 minutes. GitHub Copilot Chat's process watchdog interpreted the silence as a hung process and SIGKILLed it well before the runner finished.
|
|
12
|
+
|
|
13
|
+
**Fix shipped (v5.7.4, 2026-05-29)**:
|
|
14
|
+
|
|
15
|
+
1. **`discover.mjs` streams per-source progress to stderr** before and after each WorkIQ call:
|
|
16
|
+
```
|
|
17
|
+
[discover] workiq: <path>
|
|
18
|
+
[discover] sources: email, teams, meetings, ... (timeout 90000ms each)
|
|
19
|
+
[discover] → email ...
|
|
20
|
+
[discover] email ok in 12340ms (3 row(s))
|
|
21
|
+
[discover] → teams ...
|
|
22
|
+
```
|
|
23
|
+
stdout stays reserved for the final JSON envelope (so machine consumers are unaffected).
|
|
24
|
+
2. **Default `--timeout-ms` bumped from 60s to 90s** to absorb WorkIQ cold-start auth latency on the first call.
|
|
25
|
+
3. **New regression test**: spawns discover with a fake workiq shim and asserts `[discover]` lines hit stderr.
|
|
26
|
+
|
|
27
|
+
**Lesson — applies to every long-running runner**: any runner that takes >5s without emitting output is a host-watchdog timebomb. Two-channel discipline:
|
|
28
|
+
|
|
29
|
+
- **stdout** = final structured envelope only (one JSON line, machine-readable).
|
|
30
|
+
- **stderr** = streaming human-readable progress (every step, every long-running call).
|
|
31
|
+
|
|
32
|
+
Adopting this convention now for all `plugin/runners/*.mjs` that loop over remote calls. Future runners should `process.stderr.write('[<runner>] step description\n')` before/after each external operation.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
7
36
|
### 2026-05-29 — `<auto>` placeholder check was blocking fresh installs
|
|
8
37
|
|
|
9
38
|
**Symptom**: After a clean `npx kushi-agents@latest --all-hosts` install, the very first command users tried (`& '.\.kushi\lib\Get-KushiConfig.ps1' -Name 'm365-auth'`) threw a placeholder error citing `<auto>`. But `<auto>` is the explicit "Kushi will resolve this at runtime from identity / OS / WorkIQ" sentinel, by design — not an unfilled placeholder.
|
|
@@ -24,7 +24,7 @@ import { ask as workiqAsk, resolveWorkiqBin } from './lib/workiq.mjs';
|
|
|
24
24
|
const ALL_SOURCES = ['email', 'teams', 'meetings', 'onenote', 'sharepoint', 'crm', 'ado'];
|
|
25
25
|
|
|
26
26
|
function parseArgs(argv) {
|
|
27
|
-
const args = { force: false, dryRun: false, timeoutMs:
|
|
27
|
+
const args = { force: false, dryRun: false, timeoutMs: 90_000, sources: null };
|
|
28
28
|
for (let i = 0; i < argv.length; i++) {
|
|
29
29
|
const a = argv[i];
|
|
30
30
|
if (a === '--project') args.project = argv[++i];
|
|
@@ -32,7 +32,7 @@ function parseArgs(argv) {
|
|
|
32
32
|
else if (a === '--source') (args.sources ??= []).push(argv[++i]);
|
|
33
33
|
else if (a === '--force') args.force = true;
|
|
34
34
|
else if (a === '--dry-run') args.dryRun = true;
|
|
35
|
-
else if (a === '--timeout-ms') args.timeoutMs = Number(argv[++i]) ||
|
|
35
|
+
else if (a === '--timeout-ms') args.timeoutMs = Number(argv[++i]) || 90_000;
|
|
36
36
|
else if (a === '--help' || a === '-h') args.help = true;
|
|
37
37
|
}
|
|
38
38
|
return args;
|
|
@@ -201,17 +201,27 @@ async function main() {
|
|
|
201
201
|
let boundsDirty = false;
|
|
202
202
|
let integDirty = false;
|
|
203
203
|
|
|
204
|
+
// Stream per-source progress to stderr so users see the runner is alive.
|
|
205
|
+
// stdout is reserved for the final JSON envelope.
|
|
206
|
+
const log = (msg) => process.stderr.write(`[discover] ${msg}\n`);
|
|
207
|
+
log(`workiq: ${workiqBin}`);
|
|
208
|
+
log(`sources: ${sourcesToRun.join(', ')} (timeout ${args.timeoutMs}ms each)`);
|
|
209
|
+
|
|
204
210
|
for (const source of sourcesToRun) {
|
|
205
211
|
const prompt = buildPrompt(source, path.basename(root));
|
|
206
212
|
let rows = [];
|
|
207
213
|
let asked = true;
|
|
208
214
|
let skipReason = null;
|
|
215
|
+
const t0 = Date.now();
|
|
216
|
+
log(`→ ${source} ...`);
|
|
209
217
|
try {
|
|
210
218
|
const { blocks } = await workiqAsk(prompt, { bin: workiqBin, timeoutMs: args.timeoutMs });
|
|
211
219
|
rows = rowsFromBlocks(blocks, source);
|
|
220
|
+
log(` ${source} ok in ${Date.now() - t0}ms (${rows.length} row(s))`);
|
|
212
221
|
} catch (e) {
|
|
213
222
|
asked = true;
|
|
214
223
|
skipReason = e.code || 'workiq-error';
|
|
224
|
+
log(` ${source} ${skipReason} after ${Date.now() - t0}ms`);
|
|
215
225
|
}
|
|
216
226
|
const { boundariesPatch, integrationsPatch, accepted } = applyRows(source, rows, bounds, integ);
|
|
217
227
|
if (boundariesPatch) {
|
|
@@ -128,3 +128,25 @@ test('discover: --dry-run does not write files', async () => {
|
|
|
128
128
|
// skipped path doesn't even check dry_run, so just confirm files_written is absent or empty
|
|
129
129
|
assert.ok(!r.json.files_written || r.json.files_written.length === 0);
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
test('discover: streams [discover] progress to stderr (host-watchdog regression v5.7.4)', async () => {
|
|
133
|
+
// Without per-source stderr progress, hosts like GitHub Copilot Chat kill
|
|
134
|
+
// the process when stdout stays silent during slow workiq calls. Fix: emit
|
|
135
|
+
// `[discover] ...` lines to stderr so the host sees activity.
|
|
136
|
+
const bs = runRunner(BOOTSTRAP, ['--project', 'progress-proj', '--alias', 'alice']);
|
|
137
|
+
assert.equal(bs.code, 0, bs.stderr);
|
|
138
|
+
|
|
139
|
+
// Create a fake workiq shim that returns an empty response immediately.
|
|
140
|
+
const fakeBin = path.join(tmpRoot, process.platform === 'win32' ? 'fake-workiq.cmd' : 'fake-workiq');
|
|
141
|
+
const fakeBody = process.platform === 'win32' ? '@echo off\r\necho.\r\n' : '#!/bin/sh\necho ""\n';
|
|
142
|
+
await fs.writeFile(fakeBin, fakeBody);
|
|
143
|
+
if (process.platform !== 'win32') await fs.chmod(fakeBin, 0o755);
|
|
144
|
+
|
|
145
|
+
const r = runRunner(RUNNER, ['--project', 'progress-proj', '--alias', 'alice', '--source', 'email'], {
|
|
146
|
+
KUSHI_WORKIQ_BIN: fakeBin,
|
|
147
|
+
});
|
|
148
|
+
assert.equal(r.code, 0, r.stderr);
|
|
149
|
+
assert.match(r.stderr, /\[discover\] workiq:/, `expected workiq header on stderr, got: ${r.stderr}`);
|
|
150
|
+
assert.match(r.stderr, /\[discover\] sources: email/, `expected sources header on stderr, got: ${r.stderr}`);
|
|
151
|
+
assert.match(r.stderr, /\[discover\] → email/, `expected per-source progress on stderr, got: ${r.stderr}`);
|
|
152
|
+
});
|
package/src/doctor.test.mjs
CHANGED
|
@@ -20,7 +20,7 @@ if (!fs.existsSync(TESTTMP)) fs.mkdirSync(TESTTMP, { recursive: true });
|
|
|
20
20
|
|
|
21
21
|
function runDoctor(extraArgs = []) {
|
|
22
22
|
const args = ['-NoProfile', '-File', doctorPs1, '-Repo', repoRoot, ...extraArgs];
|
|
23
|
-
const r = spawnSync('pwsh', args, { encoding: 'utf-8', timeout:
|
|
23
|
+
const r = spawnSync('pwsh', args, { encoding: 'utf-8', timeout: 300_000 });
|
|
24
24
|
return { stdout: r.stdout || '', stderr: r.stderr || '', status: r.status ?? 1 };
|
|
25
25
|
}
|
|
26
26
|
|