@yemi33/minions 0.1.1936 → 0.1.1938
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/README.md +0 -2
- package/dashboard/js/command-center.js +12 -15
- package/dashboard/js/render-utils.js +20 -0
- package/dashboard/js/settings.js +1 -36
- package/dashboard.js +34 -153
- package/docs/README.md +1 -3
- package/docs/deprecated.json +24 -0
- package/docs/rfc-completion-json.md +4 -4
- package/engine/ado.js +0 -17
- package/engine/cc-worker-pool.js +54 -25
- package/engine/cli.js +0 -14
- package/engine/github.js +0 -17
- package/engine/lifecycle.js +0 -29
- package/engine/preflight.js +0 -19
- package/engine/shared.js +0 -13
- package/package.json +1 -4
- package/docs/teams-production.md +0 -370
- package/docs/teams-setup.md +0 -352
- package/engine/teams-cards.js +0 -137
- package/engine/teams.js +0 -647
package/README.md
CHANGED
|
@@ -676,8 +676,6 @@ To move to a new machine: `npm install -g @yemi33/minions && minions init --forc
|
|
|
676
676
|
github.js issues.js
|
|
677
677
|
ado.js ado-token.js ado-mcp-wrapper.js
|
|
678
678
|
ado-status.js check-status.js
|
|
679
|
-
# Notifications
|
|
680
|
-
teams.js teams-cards.js
|
|
681
679
|
# Runtime state (generated, gitignored)
|
|
682
680
|
control.json <- running/paused/stopped
|
|
683
681
|
dispatch.json <- pending/active/completed queue
|
|
@@ -397,13 +397,7 @@ function ccSwitchTab(id) {
|
|
|
397
397
|
var html = '';
|
|
398
398
|
var tools = tab._toolsUsed || [];
|
|
399
399
|
if (tools.length > 0) {
|
|
400
|
-
html += '<div style="margin-bottom:6px">';
|
|
401
|
-
tools.forEach(function(t) {
|
|
402
|
-
var name = typeof t === 'string' ? t : t.name;
|
|
403
|
-
var input = typeof t === 'string' ? {} : (t.input || {});
|
|
404
|
-
html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">●</span> ' + formatToolSummary(name, input) + '</div>';
|
|
405
|
-
});
|
|
406
|
-
html += '</div>';
|
|
400
|
+
html += '<div style="margin-bottom:6px">' + tools.map(renderToolChip).join('') + '</div>';
|
|
407
401
|
}
|
|
408
402
|
var text = tab._streamedText || '';
|
|
409
403
|
if (text) html += renderMd(text);
|
|
@@ -779,13 +773,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
779
773
|
}
|
|
780
774
|
var html = '';
|
|
781
775
|
if (toolsUsed.length > 0) {
|
|
782
|
-
html += '<div style="margin-bottom:6px">';
|
|
783
|
-
toolsUsed.forEach(function(t) {
|
|
784
|
-
var name = typeof t === 'string' ? t : t.name;
|
|
785
|
-
var input = typeof t === 'string' ? {} : (t.input || {});
|
|
786
|
-
html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">●</span> ' + formatToolSummary(name, input) + '</div>';
|
|
787
|
-
});
|
|
788
|
-
html += '</div>';
|
|
776
|
+
html += '<div style="margin-bottom:6px">' + toolsUsed.map(renderToolChip).join('') + '</div>';
|
|
789
777
|
}
|
|
790
778
|
if (streamedText) {
|
|
791
779
|
html += renderMd(streamedText);
|
|
@@ -851,10 +839,19 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
851
839
|
} else if (evt.type === 'heartbeat') {
|
|
852
840
|
return;
|
|
853
841
|
} else if (evt.type === 'tool') {
|
|
854
|
-
toolsUsed.push({ name: evt.name, input: evt.input || {} });
|
|
842
|
+
toolsUsed.push({ name: evt.name, input: evt.input || {}, id: evt.id || null, status: evt.id ? 'pending' : null });
|
|
855
843
|
if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
|
|
856
844
|
updateStreamDiv();
|
|
857
845
|
if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
|
|
846
|
+
} else if (evt.type === 'tool-update') {
|
|
847
|
+
for (var ti = 0; ti < toolsUsed.length; ti++) {
|
|
848
|
+
if (toolsUsed[ti] && toolsUsed[ti].id === evt.id) {
|
|
849
|
+
toolsUsed[ti].status = evt.status;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
|
|
854
|
+
updateStreamDiv();
|
|
858
855
|
} else if (evt.type === 'done') {
|
|
859
856
|
terminalEventSeen = true;
|
|
860
857
|
_cleanupStreamDiv();
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
// dashboard/js/render-utils.js — Shared formatting helpers for agent output rendering
|
|
2
2
|
// Depends on: escHtml() and renderMd() from utils.js (loaded before this file)
|
|
3
3
|
|
|
4
|
+
// marker + color per tool-call status. ACP `tool_call_update` flips chips from
|
|
5
|
+
// pending → completed/failed mid-stream so the user sees real progress; the
|
|
6
|
+
// non-pool (Claude direct) path emits status=null and stays on the neutral dot.
|
|
7
|
+
var _CC_TOOL_CHIP_STYLE = {
|
|
8
|
+
pending: { marker: '●', color: 'var(--muted)' },
|
|
9
|
+
completed: { marker: '✓', color: 'var(--green, #4ade80)' },
|
|
10
|
+
failed: { marker: '✕', color: 'var(--red, #ef4444)' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function renderToolChip(t) {
|
|
14
|
+
var name = typeof t === 'string' ? t : t.name;
|
|
15
|
+
var input = typeof t === 'string' ? {} : (t.input || {});
|
|
16
|
+
var status = typeof t === 'string' ? null : (t.status || null);
|
|
17
|
+
var style = _CC_TOOL_CHIP_STYLE[status] || _CC_TOOL_CHIP_STYLE.pending;
|
|
18
|
+
return '<div style="color:' + style.color + ';font-size:10px;font-family:monospace">'
|
|
19
|
+
+ '<span style="flex-shrink:0">' + style.marker + '</span> '
|
|
20
|
+
+ formatToolSummary(name, input)
|
|
21
|
+
+ '</div>';
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Returns a one-line human-readable summary for a Claude tool call.
|
|
6
26
|
* @param {string} name - Tool name (e.g. 'Bash', 'Read', 'Edit')
|
package/dashboard/js/settings.js
CHANGED
|
@@ -45,7 +45,6 @@ async function openSettings() {
|
|
|
45
45
|
const e = data.engine || {};
|
|
46
46
|
const c = data.claude || {};
|
|
47
47
|
const agents = data.agents || {};
|
|
48
|
-
const t = data.teams || {};
|
|
49
48
|
|
|
50
49
|
// Per-agent override placeholders surface the inherited fleet defaults as
|
|
51
50
|
// muted text — operators see exactly what each agent will resolve to without
|
|
@@ -242,28 +241,6 @@ async function openSettings() {
|
|
|
242
241
|
'</details>' +
|
|
243
242
|
'</div>' +
|
|
244
243
|
|
|
245
|
-
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Teams Integration</h3>' +
|
|
246
|
-
'<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:8px">' +
|
|
247
|
-
settingsToggle('Enable Teams', 'set-teams-enabled', !!t.enabled, 'Connect Minions to Microsoft Teams') +
|
|
248
|
-
'</div>' +
|
|
249
|
-
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">' +
|
|
250
|
-
settingsField('App ID', 'set-teams-appId', t.appId || '', '', 'Microsoft App ID from Azure Bot Configuration') +
|
|
251
|
-
settingsField('App Password', 'set-teams-appPassword', t.appPassword || '', '', 'Client secret (leave blank for certificate auth)') +
|
|
252
|
-
'</div>' +
|
|
253
|
-
'<div style="font-size:10px;color:var(--muted);margin-bottom:4px;font-weight:600">Certificate Auth (alternative to client secret)</div>' +
|
|
254
|
-
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">' +
|
|
255
|
-
settingsField('Certificate Path', 'set-teams-certPath', t.certPath || '', '', 'Path to PEM certificate file') +
|
|
256
|
-
settingsField('Private Key Path', 'set-teams-privateKeyPath', t.privateKeyPath || '', '', 'Path to PEM private key file') +
|
|
257
|
-
settingsField('Tenant ID', 'set-teams-tenantId', t.tenantId || '', '', 'Azure AD tenant ID (required for cert auth)') +
|
|
258
|
-
'</div>' +
|
|
259
|
-
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
260
|
-
settingsField('Notify Events', 'set-teams-notifyEvents', (t.notifyEvents || []).join(', '), '', 'Comma-separated event types to notify') +
|
|
261
|
-
settingsField('Inbox Poll Interval', 'set-teams-inboxPollInterval', t.inboxPollInterval || 15000, 'ms', 'How often to check for Teams messages') +
|
|
262
|
-
'</div>' +
|
|
263
|
-
'<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:16px">' +
|
|
264
|
-
settingsToggle('CC Mirror', 'set-teams-ccMirror', t.ccMirror !== false, 'Mirror Command Center responses to Teams') +
|
|
265
|
-
'</div>' +
|
|
266
|
-
|
|
267
244
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Claude CLI</h3>' +
|
|
268
245
|
'<div style="display:grid;grid-template-columns:1fr;gap:8px;margin-bottom:8px">' +
|
|
269
246
|
settingsField('Allowed Tools', 'set-allowedTools', c.allowedTools || '', '', 'Claude allow-list passed through for compatibility; runtime bypass flags are adapter-owned.') +
|
|
@@ -637,18 +614,6 @@ async function saveSettings() {
|
|
|
637
614
|
allowedTools: document.getElementById('set-allowedTools').value,
|
|
638
615
|
};
|
|
639
616
|
|
|
640
|
-
const teamsPayload = {
|
|
641
|
-
enabled: document.getElementById('set-teams-enabled').checked,
|
|
642
|
-
appId: document.getElementById('set-teams-appId').value,
|
|
643
|
-
appPassword: document.getElementById('set-teams-appPassword').value,
|
|
644
|
-
certPath: document.getElementById('set-teams-certPath').value,
|
|
645
|
-
privateKeyPath: document.getElementById('set-teams-privateKeyPath').value,
|
|
646
|
-
tenantId: document.getElementById('set-teams-tenantId').value,
|
|
647
|
-
notifyEvents: document.getElementById('set-teams-notifyEvents').value,
|
|
648
|
-
inboxPollInterval: document.getElementById('set-teams-inboxPollInterval').value,
|
|
649
|
-
ccMirror: document.getElementById('set-teams-ccMirror').checked,
|
|
650
|
-
};
|
|
651
|
-
|
|
652
617
|
const agentsPayload = {};
|
|
653
618
|
document.querySelectorAll('[data-agent][data-field]').forEach(function(el) {
|
|
654
619
|
const id = el.dataset.agent;
|
|
@@ -671,7 +636,7 @@ async function saveSettings() {
|
|
|
671
636
|
// Save config
|
|
672
637
|
const res = await fetch('/api/settings', {
|
|
673
638
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
674
|
-
body: JSON.stringify({ engine: enginePayload, claude: claudePayload, agents: agentsPayload,
|
|
639
|
+
body: JSON.stringify({ engine: enginePayload, claude: claudePayload, agents: agentsPayload, projects: projectsPayload })
|
|
675
640
|
});
|
|
676
641
|
const result = await res.json();
|
|
677
642
|
if (!res.ok) throw new Error(result.error);
|
package/dashboard.js
CHANGED
|
@@ -21,7 +21,6 @@ const _dashboardVersion = {
|
|
|
21
21
|
};
|
|
22
22
|
const shared = require('./engine/shared');
|
|
23
23
|
const queries = require('./engine/queries');
|
|
24
|
-
const teams = require('./engine/teams');
|
|
25
24
|
const ado = require('./engine/ado');
|
|
26
25
|
const gh = require('./engine/github');
|
|
27
26
|
const issues = require('./engine/issues');
|
|
@@ -121,10 +120,6 @@ function mergeSettingsConfigUpdate(current, candidate, body, patch = {}) {
|
|
|
121
120
|
if (candidate.agents && candidate.agents[id]) current.agents[id] = candidate.agents[id];
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
|
-
if (body.teams) {
|
|
125
|
-
if (candidate.teams) current.teams = candidate.teams;
|
|
126
|
-
else delete current.teams;
|
|
127
|
-
}
|
|
128
123
|
if (body.projects && Array.isArray(body.projects)) {
|
|
129
124
|
if (!Array.isArray(current.projects)) current.projects = [];
|
|
130
125
|
for (const update of body.projects) {
|
|
@@ -804,7 +799,6 @@ function _steeringDeliveryState(agentId) {
|
|
|
804
799
|
}
|
|
805
800
|
|
|
806
801
|
const PLANS_DIR = path.join(MINIONS_DIR, 'plans');
|
|
807
|
-
const TEAMS_INBOX_PATH = path.join(ENGINE_DIR, 'teams-inbox.json');
|
|
808
802
|
|
|
809
803
|
// Resolve a plan/PRD file path: .json files live in prd/, .md files in plans/
|
|
810
804
|
// Validates that the file stays within the expected directory to prevent path traversal.
|
|
@@ -2018,7 +2012,6 @@ const CC_API_FALLBACK_METHODS = new Set(['GET', 'POST', 'DELETE']);
|
|
|
2018
2012
|
const CC_API_FALLBACK_BLOCKED_PREFIXES = [
|
|
2019
2013
|
'/api/command-center',
|
|
2020
2014
|
'/api/doc-chat',
|
|
2021
|
-
'/api/bot',
|
|
2022
2015
|
];
|
|
2023
2016
|
|
|
2024
2017
|
// SoT for CC's runtime API index. Captured lazily on the first HTTP request
|
|
@@ -2498,7 +2491,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
|
|
|
2498
2491
|
* document body. Always re-sending extraContext is correctness-safe; the
|
|
2499
2492
|
* pool's warm-process saving is preserved regardless.
|
|
2500
2493
|
*/
|
|
2501
|
-
function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk, onToolUse }) {
|
|
2494
|
+
function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemPrompt, sessionKey, freshSession, timeoutMs, onChunk, onToolUse, onToolUpdate }) {
|
|
2502
2495
|
const oneShot = !!freshSession;
|
|
2503
2496
|
const tabKey = oneShot
|
|
2504
2497
|
? 'doc-chat:fresh:' + shared.uid()
|
|
@@ -2576,9 +2569,14 @@ function _invokeDocChatViaPool({ prompt, model, effort, engineConfig, systemProm
|
|
|
2576
2569
|
try { onChunk(accumulated); } catch { /* swallow */ }
|
|
2577
2570
|
}
|
|
2578
2571
|
},
|
|
2579
|
-
onToolUse: (name, input) => {
|
|
2572
|
+
onToolUse: (name, input, toolCallId) => {
|
|
2580
2573
|
if (onToolUse) {
|
|
2581
|
-
try { onToolUse(name, input || {}); } catch { /* swallow */ }
|
|
2574
|
+
try { onToolUse(name, input || {}, toolCallId); } catch { /* swallow */ }
|
|
2575
|
+
}
|
|
2576
|
+
},
|
|
2577
|
+
onToolUpdate: (toolCallId, status) => {
|
|
2578
|
+
if (onToolUpdate) {
|
|
2579
|
+
try { onToolUpdate(toolCallId, status); } catch { /* swallow */ }
|
|
2582
2580
|
}
|
|
2583
2581
|
},
|
|
2584
2582
|
onDone: () => {
|
|
@@ -2762,7 +2760,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2762
2760
|
return result;
|
|
2763
2761
|
}
|
|
2764
2762
|
|
|
2765
|
-
async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, skipPreflight = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript, turnId, freshSession = false } = {}) {
|
|
2763
|
+
async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, skipPreflight = false, model, onAbortReady, onChunk, onToolUse, onToolUpdate, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript, turnId, freshSession = false } = {}) {
|
|
2766
2764
|
if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
|
|
2767
2765
|
if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
|
|
2768
2766
|
const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
|
|
@@ -2793,7 +2791,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2793
2791
|
const p = _invokeDocChatViaPool({
|
|
2794
2792
|
prompt: poolPrompt, sessionKey, model, effort: ccEffort,
|
|
2795
2793
|
engineConfig: CONFIG.engine, systemPrompt,
|
|
2796
|
-
onChunk, onToolUse,
|
|
2794
|
+
onChunk, onToolUse, onToolUpdate,
|
|
2797
2795
|
freshSession, timeoutMs: timeout,
|
|
2798
2796
|
});
|
|
2799
2797
|
if (onAbortReady) onAbortReady(p.abort);
|
|
@@ -5034,9 +5032,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
5034
5032
|
}
|
|
5035
5033
|
}
|
|
5036
5034
|
|
|
5037
|
-
// Teams notification for plan approval — non-blocking
|
|
5038
|
-
try { teams.teamsNotifyPlanEvent({ name: plan.plan_summary || body.file, file: body.file }, 'plan-approved').catch(() => {}); } catch {}
|
|
5039
|
-
|
|
5040
5035
|
invalidateStatusCache();
|
|
5041
5036
|
return jsonReply(res, 200, { ok: true, status: 'approved', resumedWorkItems: resumed, diffAwareUpdate: diffAwareQueued });
|
|
5042
5037
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
@@ -5204,9 +5199,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
5204
5199
|
return data;
|
|
5205
5200
|
}, { defaultValue: {} });
|
|
5206
5201
|
|
|
5207
|
-
// Teams notification for plan rejection — non-blocking
|
|
5208
|
-
try { teams.teamsNotifyPlanEvent({ name: plan.plan_summary || body.file, file: body.file }, 'plan-rejected').catch(() => {}); } catch {}
|
|
5209
|
-
|
|
5210
5202
|
return jsonReply(res, 200, { ok: true, status: 'rejected' });
|
|
5211
5203
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
5212
5204
|
}
|
|
@@ -6163,17 +6155,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6163
6155
|
const abort = ccInFlightAborts.get(tabId);
|
|
6164
6156
|
if (abort) { try { abort(); } catch {} }
|
|
6165
6157
|
}
|
|
6166
|
-
//
|
|
6167
|
-
//
|
|
6168
|
-
// remote daemon stops generating into a torn-down session. The pool
|
|
6169
|
-
// exposes cancellation via the SessionHandle returned from getSession;
|
|
6170
|
-
// we don't keep that handle around here, so route through closeTab to
|
|
6171
|
-
// both cancel inflight and tear down the worker (cheaper than tracking
|
|
6172
|
-
// per-tab handles in dashboard state, and matches "tab close" semantics
|
|
6173
|
-
// — if the user explicitly aborted, we don't owe them a warm process).
|
|
6174
|
-
// Off when the flag is off so legacy SIGTERM-only behavior is preserved.
|
|
6158
|
+
// Cancel the inflight ACP turn but keep the warm worker; closing the
|
|
6159
|
+
// tab here would force the next turn to pay the ~7-8 s cold-spawn.
|
|
6175
6160
|
if (shared.resolveCcUseWorkerPool(CONFIG.engine)) {
|
|
6176
|
-
try { ccWorkerPool.
|
|
6161
|
+
try { ccWorkerPool.cancelInflight(tabId); } catch { /* swallow */ }
|
|
6177
6162
|
}
|
|
6178
6163
|
_clearCcLiveStream(tabId);
|
|
6179
6164
|
_releaseCCTab(tabId);
|
|
@@ -6275,10 +6260,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6275
6260
|
sessionId: ccSession.sessionId,
|
|
6276
6261
|
newSession: !wasResume,
|
|
6277
6262
|
};
|
|
6278
|
-
// Mirror user-facing text to Teams (skip Teams-originated turns).
|
|
6279
|
-
if (!tabId.startsWith('teams-')) {
|
|
6280
|
-
teams.teamsPostCCResponse(body.message, result.text).catch(() => {});
|
|
6281
|
-
}
|
|
6282
6263
|
if (sessionReset) replyBody.sessionReset = true;
|
|
6283
6264
|
return jsonReply(res, 200, replyBody);
|
|
6284
6265
|
} finally {
|
|
@@ -6333,9 +6314,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6333
6314
|
},
|
|
6334
6315
|
onToolUse: (name, input) => {
|
|
6335
6316
|
_touchCcLiveStream(liveState);
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6317
|
+
const entry = { name, input: input || {}, id: null, status: null };
|
|
6318
|
+
toolUses.push(entry);
|
|
6319
|
+
liveState.tools.push(entry);
|
|
6320
|
+
if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(input), id: null });
|
|
6339
6321
|
},
|
|
6340
6322
|
});
|
|
6341
6323
|
}
|
|
@@ -6431,14 +6413,26 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6431
6413
|
liveState.text = accumulated;
|
|
6432
6414
|
if (liveState.writer) liveState.writer({ type: 'chunk', text: accumulated });
|
|
6433
6415
|
},
|
|
6434
|
-
onToolUse: (name, input) => {
|
|
6416
|
+
onToolUse: (name, input, toolCallId) => {
|
|
6435
6417
|
if (_tFirstTool == null) _tFirstTool = Date.now();
|
|
6436
6418
|
_toolUseCount += 1;
|
|
6437
6419
|
_touchCcLiveStream(liveState);
|
|
6438
6420
|
const safeInput = input || {};
|
|
6439
|
-
|
|
6440
|
-
if (Array.isArray(
|
|
6441
|
-
if (liveState.
|
|
6421
|
+
const entry = { name, input: safeInput, id: toolCallId || null, status: toolCallId ? 'pending' : null };
|
|
6422
|
+
if (Array.isArray(toolUses)) toolUses.push(entry);
|
|
6423
|
+
if (Array.isArray(liveState.tools)) liveState.tools.push(entry);
|
|
6424
|
+
if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(safeInput), id: toolCallId || null });
|
|
6425
|
+
},
|
|
6426
|
+
onToolUpdate: (toolCallId, status) => {
|
|
6427
|
+
_touchCcLiveStream(liveState);
|
|
6428
|
+
// toolUses and liveState.tools hold the same entry references (both
|
|
6429
|
+
// populated via onToolUse above), so mutating in one side reflects
|
|
6430
|
+
// in the other. Patch once.
|
|
6431
|
+
if (Array.isArray(toolUses)) {
|
|
6432
|
+
const entry = toolUses.find((e) => e && e.id === toolCallId);
|
|
6433
|
+
if (entry) entry.status = status;
|
|
6434
|
+
}
|
|
6435
|
+
if (liveState.writer) liveState.writer({ type: 'tool-update', id: toolCallId, status });
|
|
6442
6436
|
},
|
|
6443
6437
|
onDone: () => {
|
|
6444
6438
|
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'done');
|
|
@@ -6748,12 +6742,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6748
6742
|
liveState.donePayload = donePayload;
|
|
6749
6743
|
if (liveState.writer) liveState.writer(donePayload);
|
|
6750
6744
|
|
|
6751
|
-
// Mirror CC response to Teams (non-blocking, skip Teams-originated)
|
|
6752
|
-
const _streamTabId = body.tabId || 'default';
|
|
6753
|
-
if (!_streamTabId.startsWith('teams-')) {
|
|
6754
|
-
teams.teamsPostCCResponse(body.message, displayText).catch(() => {});
|
|
6755
|
-
}
|
|
6756
|
-
|
|
6757
6745
|
if (liveState.endResponse) liveState.endResponse();
|
|
6758
6746
|
_scheduleCcLiveCleanup(tabId);
|
|
6759
6747
|
} finally {
|
|
@@ -7089,7 +7077,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7089
7077
|
engine,
|
|
7090
7078
|
claude: settingsClaudeConfig(config),
|
|
7091
7079
|
agents: config.agents || {},
|
|
7092
|
-
teams: { ...shared.ENGINE_DEFAULTS.teams, ...(config.teams || {}) },
|
|
7093
7080
|
projects: (config.projects || []).map(p => ({
|
|
7094
7081
|
name: p.name,
|
|
7095
7082
|
workSources: {
|
|
@@ -7377,22 +7364,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7377
7364
|
}
|
|
7378
7365
|
}
|
|
7379
7366
|
|
|
7380
|
-
if (body.teams) {
|
|
7381
|
-
if (!config.teams) config.teams = {};
|
|
7382
|
-
const tm = body.teams;
|
|
7383
|
-
if (tm.enabled !== undefined) config.teams.enabled = !!tm.enabled;
|
|
7384
|
-
for (const key of ['appId', 'appPassword', 'certPath', 'privateKeyPath', 'tenantId']) {
|
|
7385
|
-
if (tm[key] !== undefined) config.teams[key] = String(tm[key] || '');
|
|
7386
|
-
}
|
|
7387
|
-
if (tm.notifyEvents !== undefined) {
|
|
7388
|
-
config.teams.notifyEvents = Array.isArray(tm.notifyEvents) ? tm.notifyEvents : String(tm.notifyEvents || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
7389
|
-
}
|
|
7390
|
-
if (tm.inboxPollInterval !== undefined) config.teams.inboxPollInterval = Math.max(5000, Number(tm.inboxPollInterval) || 15000);
|
|
7391
|
-
if (tm.ccMirror !== undefined) config.teams.ccMirror = !!tm.ccMirror;
|
|
7392
|
-
// Invalidate cached adapter so credential changes take effect
|
|
7393
|
-
teams._resetAdapter();
|
|
7394
|
-
}
|
|
7395
|
-
|
|
7396
7367
|
if (body.projects && Array.isArray(body.projects)) {
|
|
7397
7368
|
if (!config.projects) config.projects = [];
|
|
7398
7369
|
for (const update of body.projects) {
|
|
@@ -7540,93 +7511,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7540
7511
|
}
|
|
7541
7512
|
}
|
|
7542
7513
|
|
|
7543
|
-
// ── Teams Bot Handler ─────────────────────────────────────────────────────
|
|
7544
|
-
|
|
7545
|
-
async function handleTeamsBot(req, res) {
|
|
7546
|
-
if (!teams.isTeamsEnabled()) {
|
|
7547
|
-
return jsonReply(res, 503, { error: 'Teams integration disabled' }, req);
|
|
7548
|
-
}
|
|
7549
|
-
const adapter = teams.createAdapter();
|
|
7550
|
-
if (!adapter) {
|
|
7551
|
-
return jsonReply(res, 503, { error: 'Teams adapter unavailable' }, req);
|
|
7552
|
-
}
|
|
7553
|
-
try {
|
|
7554
|
-
await adapter.process(req, res, async (context) => {
|
|
7555
|
-
const activity = context.activity;
|
|
7556
|
-
const cfg = teams.getTeamsConfig();
|
|
7557
|
-
|
|
7558
|
-
// Save conversation reference on install/member events
|
|
7559
|
-
if (activity.type === 'conversationUpdate' && activity.membersAdded?.length) {
|
|
7560
|
-
const ref = context.activity.conversation?.id;
|
|
7561
|
-
if (ref) {
|
|
7562
|
-
const convRef = {
|
|
7563
|
-
activityId: activity.id,
|
|
7564
|
-
user: activity.from,
|
|
7565
|
-
bot: activity.recipient,
|
|
7566
|
-
conversation: activity.conversation,
|
|
7567
|
-
channelId: activity.channelId,
|
|
7568
|
-
locale: activity.locale,
|
|
7569
|
-
serviceUrl: activity.serviceUrl,
|
|
7570
|
-
};
|
|
7571
|
-
teams.saveConversationRef(activity.conversation.id, convRef);
|
|
7572
|
-
shared.log('info', `Teams conversationUpdate: saved ref for ${activity.conversation.id}`);
|
|
7573
|
-
}
|
|
7574
|
-
}
|
|
7575
|
-
|
|
7576
|
-
if (activity.type === 'installationUpdate') {
|
|
7577
|
-
const convRef = {
|
|
7578
|
-
activityId: activity.id,
|
|
7579
|
-
user: activity.from,
|
|
7580
|
-
bot: activity.recipient,
|
|
7581
|
-
conversation: activity.conversation,
|
|
7582
|
-
channelId: activity.channelId,
|
|
7583
|
-
locale: activity.locale,
|
|
7584
|
-
serviceUrl: activity.serviceUrl,
|
|
7585
|
-
};
|
|
7586
|
-
if (activity.conversation?.id) {
|
|
7587
|
-
teams.saveConversationRef(activity.conversation.id, convRef);
|
|
7588
|
-
shared.log('info', `Teams installationUpdate: saved ref for ${activity.conversation.id}`);
|
|
7589
|
-
}
|
|
7590
|
-
}
|
|
7591
|
-
|
|
7592
|
-
// Handle incoming messages
|
|
7593
|
-
if (activity.type === 'message' && activity.text) {
|
|
7594
|
-
// Filter bot's own echo messages
|
|
7595
|
-
if (activity.from?.id === cfg.appId) return;
|
|
7596
|
-
|
|
7597
|
-
const msgId = `teams-${Date.now()}-${shared.uid()}`;
|
|
7598
|
-
const convRef = {
|
|
7599
|
-
activityId: activity.id,
|
|
7600
|
-
user: activity.from,
|
|
7601
|
-
bot: activity.recipient,
|
|
7602
|
-
conversation: activity.conversation,
|
|
7603
|
-
channelId: activity.channelId,
|
|
7604
|
-
locale: activity.locale,
|
|
7605
|
-
serviceUrl: activity.serviceUrl,
|
|
7606
|
-
};
|
|
7607
|
-
mutateJsonFileLocked(TEAMS_INBOX_PATH, (inbox) => {
|
|
7608
|
-
if (!Array.isArray(inbox)) inbox = [];
|
|
7609
|
-
inbox.push({
|
|
7610
|
-
id: msgId,
|
|
7611
|
-
text: activity.text,
|
|
7612
|
-
from: activity.from?.name || activity.from?.id || 'unknown',
|
|
7613
|
-
conversationRef: convRef,
|
|
7614
|
-
receivedAt: new Date().toISOString(),
|
|
7615
|
-
_processedAt: null,
|
|
7616
|
-
});
|
|
7617
|
-
return inbox;
|
|
7618
|
-
}, { defaultValue: [] });
|
|
7619
|
-
shared.log('info', `Teams message received from ${activity.from?.name || 'unknown'}: ${activity.text.slice(0, 80)}`);
|
|
7620
|
-
}
|
|
7621
|
-
});
|
|
7622
|
-
} catch (err) {
|
|
7623
|
-
shared.log('warn', `Teams bot handler error: ${err.message}`);
|
|
7624
|
-
if (!res.headersSent) {
|
|
7625
|
-
return jsonReply(res, 500, { error: 'Bot processing failed' }, req);
|
|
7626
|
-
}
|
|
7627
|
-
}
|
|
7628
|
-
}
|
|
7629
|
-
|
|
7630
7514
|
// ── Route Registry ──────────────────────────────────────────────────────────
|
|
7631
7515
|
// Order matters: specific routes before general ones (e.g., /api/plans/approve before /api/plans/:file)
|
|
7632
7516
|
|
|
@@ -8311,16 +8195,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8311
8195
|
|
|
8312
8196
|
// Settings
|
|
8313
8197
|
{ method: 'GET', path: '/api/settings', desc: 'Return current engine + claude + routing config', handler: handleSettingsRead },
|
|
8314
|
-
{ method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent +
|
|
8198
|
+
{ method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent + projects config', params: 'engine?, claude?, agents?, projects?', handler: handleSettingsUpdate },
|
|
8315
8199
|
{ method: 'POST', path: '/api/settings/routing', desc: 'Update routing.md', params: 'content', handler: handleSettingsRouting },
|
|
8316
8200
|
{ method: 'POST', path: '/api/settings/reset', desc: 'Reset engine + claude + agent settings to defaults', handler: handleSettingsReset },
|
|
8317
8201
|
|
|
8318
8202
|
// Feature flags (experimental / in-progress UX gates — see engine/features.js)
|
|
8319
8203
|
{ method: 'GET', path: '/api/features', desc: 'List registered feature flags with current enabled state', handler: handleFeaturesList },
|
|
8320
8204
|
{ method: 'POST', path: '/api/features/toggle', desc: 'Enable/disable a registered feature flag', params: 'id, enabled', handler: handleFeaturesToggle },
|
|
8321
|
-
|
|
8322
|
-
// Teams Bot Framework webhook
|
|
8323
|
-
{ method: 'POST', path: '/api/bot', desc: 'Bot Framework webhook for Teams integration', handler: handleTeamsBot },
|
|
8324
8205
|
];
|
|
8325
8206
|
|
|
8326
8207
|
// ── Route Dispatcher ────────────────────────────────────────────────────────
|
package/docs/README.md
CHANGED
|
@@ -23,13 +23,11 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
23
23
|
|
|
24
24
|
## Operations
|
|
25
25
|
|
|
26
|
-
Operational runbooks for engine operators
|
|
26
|
+
Operational runbooks for engine operators and fleet maintainers.
|
|
27
27
|
|
|
28
28
|
- [auto-discovery.md](auto-discovery.md) — Auto-discovery and execution pipeline: the per-tick orchestration loop and the four work-discovery sources.
|
|
29
29
|
- [engine-restart.md](engine-restart.md) — How agents survive an engine restart: state persistence, the 20-minute startup grace period, and orphan reattachment via PID files and `live-output.log`.
|
|
30
30
|
- [human-vs-automated.md](human-vs-automated.md) — Quick reference table of which features humans start, run, decide, and recover, and the two human approval gates.
|
|
31
|
-
- [teams-production.md](teams-production.md) — Three deployment options (Azure App Service, Container Apps, self-hosted VM) for migrating the Teams bot from a Dev Tunnel to a stable HTTPS endpoint.
|
|
32
|
-
- [teams-setup.md](teams-setup.md) — End-to-end guide for connecting Minions to Microsoft Teams via Azure Bot Framework and a Dev Tunnel.
|
|
33
31
|
|
|
34
32
|
---
|
|
35
33
|
|
package/docs/deprecated.json
CHANGED
|
@@ -14,6 +14,30 @@
|
|
|
14
14
|
"reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale on-disk values from old engine versions.",
|
|
15
15
|
"targetRemovalDate": null,
|
|
16
16
|
"notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :799-800; usage at :803-815 for work items and :880-887 for PRD missing_features) can be deleted. Total cost on disk today: 4 strings."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "native-teams-integration",
|
|
20
|
+
"removedAt": "2026-05-14",
|
|
21
|
+
"reason": "Native Microsoft Teams Bot Framework integration removed end-to-end. The Teams MCP server (teams-* tools, configured in the CC client outside this repo) supersedes the in-repo Bot Framework path. Removal closes the dual-implementation gap and drops the botbuilder dependency.",
|
|
22
|
+
"removedLocations": [
|
|
23
|
+
"engine/teams.js",
|
|
24
|
+
"engine/teams-cards.js",
|
|
25
|
+
"engine/teams-state.json (runtime state)",
|
|
26
|
+
"engine/teams-inbox.json (runtime state, generated by the deleted /api/bot handler)",
|
|
27
|
+
"dashboard.js: POST /api/bot route + handleTeamsBot, TEAMS_INBOX_PATH constant, CC mirror hooks (teamsPostCCResponse), plan-approval/rejection Teams notifications, settings GET/POST teams block",
|
|
28
|
+
"dashboard/js/settings.js: Teams Integration settings UI + teamsPayload submit",
|
|
29
|
+
"engine/lifecycle.js: teamsNotifyCompletion, teamsNotifyPlanEvent (verify-created + plan-completed), teamsNotifyPrEvent (post-merge)",
|
|
30
|
+
"engine/github.js + engine/ado.js: teamsNotifyPrEvent on pr-approved and build-failed",
|
|
31
|
+
"engine/preflight.js: Teams integration doctor check",
|
|
32
|
+
"engine/cli.js: teamsInboxTimer + clearInterval on shutdown",
|
|
33
|
+
"engine/shared.js: ENGINE_DEFAULTS.teams block",
|
|
34
|
+
"package.json: botbuilder dependency (4.23.3)",
|
|
35
|
+
"docs/teams-setup.md, docs/teams-production.md",
|
|
36
|
+
"test/unit/auto-recovery.test.js: ~58 Teams test cases",
|
|
37
|
+
"test/unit/preflight-behavioral.test.js: 4 doctor Teams checks + teams field in the docs-link coverage scenario",
|
|
38
|
+
"README.md / CLAUDE.md / docs/README.md / TODO.md / docs/rfc-completion-json.md: prose references"
|
|
39
|
+
],
|
|
40
|
+
"notes": "The Teams MCP (teams-* tools) lives outside this repo in the CC client config and is NOT affected. If Teams-style notifications are needed again, route them through the MCP layer or an external webhook watch action — do not re-introduce the Bot Framework SDK in-process."
|
|
17
41
|
}
|
|
18
42
|
]
|
|
19
43
|
|
|
@@ -101,8 +101,8 @@ The agent must not write the file in pieces. Empty, truncated, or malformed JSON
|
|
|
101
101
|
|
|
102
102
|
// ── Always required ──────────────────────────────────────────────────────
|
|
103
103
|
"status": "done", // see §4.2
|
|
104
|
-
"summary": "Added /api/
|
|
105
|
-
"filesChanged": ["engine/
|
|
104
|
+
"summary": "Added /api/example endpoint and wired example integration.", // ≤500 chars
|
|
105
|
+
"filesChanged": ["engine/example.js", "dashboard.js"], // optional, hint only
|
|
106
106
|
|
|
107
107
|
// ── PR control plane (replaces sites 1, 4 for fix/implement/verify) ─────
|
|
108
108
|
"prs": [
|
|
@@ -271,7 +271,7 @@ These paths stay on stdout / live-output.log:
|
|
|
271
271
|
|
|
272
272
|
1. **`engine/timeout.js` completion-via-output detection** (`timeout.js:189-219`). The signal is the engine-written `[process-exit]` sentinel, emitted even if the agent crashed before writing completion.json. Removing it would mean orphans that finished during process-handle loss are never reconciled.
|
|
273
273
|
2. **Stale-orphan cleanup via `live-output.log` mtime** (`timeout.js:178`). Completion.json is written once at exit, so `live-output.log` remains the best indirect signal after the engine loses process tracking.
|
|
274
|
-
3. **`parseStreamJsonOutput` for `resultSummary`** in `parseAgentOutput` (`lifecycle.js:1483`). This extracts the human-readable summary from the CLI's stream-json. Even after the flip, `completion.summary` is *also* extracted, but the stream-json text remains the canonical "what did the agent say last" — used in dashboards, agent history,
|
|
274
|
+
3. **`parseStreamJsonOutput` for `resultSummary`** in `parseAgentOutput` (`lifecycle.js:1483`). This extracts the human-readable summary from the CLI's stream-json. Even after the flip, `completion.summary` is *also* extracted, but the stream-json text remains the canonical "what did the agent say last" — used in dashboards, agent history, and external notifications. The two coexist: `completion.summary` is for routing decisions, the stream-json text is for display.
|
|
275
275
|
4. **Inbox-file skill scan** (`lifecycle.js:2013-2024`). Some agents write skills into their inbox findings file (a deliberate human-discoverable artifact). The completion file deprecates inline ` ```skill ` blocks in stdout, but the inbox file scan is opt-in and stays — it's a different surface (a real file the agent intentionally wrote, not regex-scraped from stdout).
|
|
276
276
|
|
|
277
277
|
### 5.5 Backward Compatibility
|
|
@@ -355,7 +355,7 @@ engine falls back to stdout parsing during the dual-mode period. After the
|
|
|
355
355
|
flip date, missing/invalid completion.json marks your dispatch failed.
|
|
356
356
|
|
|
357
357
|
Do NOT include sensitive data (tokens, API keys) — completion.json is read
|
|
358
|
-
by the engine and may surface in dashboard views and
|
|
358
|
+
by the engine and may surface in dashboard views and external notifications.
|
|
359
359
|
```
|
|
360
360
|
|
|
361
361
|
### 7.2 Per-Playbook Removals
|
package/engine/ado.js
CHANGED
|
@@ -860,14 +860,6 @@ async function pollPrStatus(config) {
|
|
|
860
860
|
pr.reviewStatus = newReviewStatus;
|
|
861
861
|
updated = true;
|
|
862
862
|
shared.trackReviewMetric(pr, newReviewStatus, config);
|
|
863
|
-
if (newReviewStatus === 'approved') {
|
|
864
|
-
// Teams notification for PR approval — non-blocking, edge-triggered (only on transition)
|
|
865
|
-
try {
|
|
866
|
-
const teams = require('./teams');
|
|
867
|
-
const prFilePath = shared.projectPrPath(project);
|
|
868
|
-
teams.teamsNotifyPrEvent(pr, 'pr-approved', project, prFilePath).catch(() => {});
|
|
869
|
-
} catch {}
|
|
870
|
-
}
|
|
871
863
|
}
|
|
872
864
|
|
|
873
865
|
if (newStatus !== PR_STATUS.ACTIVE) return updated;
|
|
@@ -976,15 +968,6 @@ async function pollPrStatus(config) {
|
|
|
976
968
|
}
|
|
977
969
|
}
|
|
978
970
|
updated = true;
|
|
979
|
-
|
|
980
|
-
if (buildStatus === 'failing') {
|
|
981
|
-
// Teams notification for build failure — non-blocking
|
|
982
|
-
try {
|
|
983
|
-
const teams = require('./teams');
|
|
984
|
-
const prFilePath = shared.projectPrPath(project);
|
|
985
|
-
teams.teamsNotifyPrEvent(pr, 'build-failed', project, prFilePath).catch(() => {});
|
|
986
|
-
} catch {}
|
|
987
|
-
}
|
|
988
971
|
}
|
|
989
972
|
if (buildStatus === 'failing') {
|
|
990
973
|
if (buildFailReason && pr.buildFailReason !== buildFailReason) {
|