@yemi33/minions 0.1.1808 → 0.1.1810
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/CHANGELOG.md +8 -0
- package/dashboard/js/command-center.js +11 -8
- package/dashboard/js/command-input.js +2 -1
- package/dashboard/js/modal-qa.js +1 -1
- package/dashboard.js +555 -29
- package/docs/pr-review-fix-loop.md +2 -0
- package/engine/copilot-models.json +1 -1
- package/engine/pipeline.js +146 -43
- package/engine/projects.js +10 -11
- package/engine/shared.js +39 -0
- package/engine.js +47 -14
- package/package.json +1 -1
- package/playbooks/fix.md +17 -6
- package/prompts/cc-system.md +7 -2
- package/prompts/doc-chat-system.md +15 -7
- package/routing.md +1 -0
package/dashboard.js
CHANGED
|
@@ -369,14 +369,11 @@ function createWorkItemWithDedup(wiPath, item, options = {}) {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
function formatUnknownProjectError(projectName, projects = []) {
|
|
372
|
-
|
|
373
|
-
return `Project "${projectName}" not found. Known projects: ${known}`;
|
|
372
|
+
return shared.formatUnknownProjectError(projectName, projects);
|
|
374
373
|
}
|
|
375
374
|
|
|
376
375
|
function findProjectByName(projects, projectName) {
|
|
377
|
-
|
|
378
|
-
if (!name) return null;
|
|
379
|
-
return projects.find(p => p.name?.toLowerCase() === name) || null;
|
|
376
|
+
return shared.findProjectByName(projects, projectName);
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
function resolveWorkItemsCreateTarget(projectName, projects = PROJECTS) {
|
|
@@ -394,6 +391,47 @@ function resolveWorkItemsCreateTarget(projectName, projects = PROJECTS) {
|
|
|
394
391
|
};
|
|
395
392
|
}
|
|
396
393
|
|
|
394
|
+
function validatePipelineProjects(pipeline, projects = PROJECTS) {
|
|
395
|
+
const refs = [];
|
|
396
|
+
const collect = (value) => {
|
|
397
|
+
if (value === undefined || value === null || value === '') return;
|
|
398
|
+
if (Array.isArray(value)) { value.forEach(collect); return; }
|
|
399
|
+
if (typeof value === 'object') {
|
|
400
|
+
if (value.project !== undefined) collect(value.project);
|
|
401
|
+
else if (value._project !== undefined) collect(value._project);
|
|
402
|
+
else if (value.name !== undefined) collect(value.name);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
refs.push(String(value));
|
|
406
|
+
};
|
|
407
|
+
const collectResourceProjects = (resources) => {
|
|
408
|
+
for (const resource of Array.isArray(resources) ? resources : []) {
|
|
409
|
+
if (resource && typeof resource === 'object') {
|
|
410
|
+
if (resource.project !== undefined) collect(resource.project);
|
|
411
|
+
if (resource._project !== undefined) collect(resource._project);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
collect(pipeline.project);
|
|
416
|
+
collect(pipeline.projects);
|
|
417
|
+
collectResourceProjects(pipeline.monitoredResources);
|
|
418
|
+
for (const stage of pipeline.stages || []) {
|
|
419
|
+
collect(stage.project);
|
|
420
|
+
collect(stage.projects);
|
|
421
|
+
collectResourceProjects(stage.monitoredResources);
|
|
422
|
+
for (const item of stage.items || []) {
|
|
423
|
+
collect(item.project);
|
|
424
|
+
collect(item.projects);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for (const ref of refs) {
|
|
428
|
+
if (ref.trim().toLowerCase() === 'central') continue;
|
|
429
|
+
const result = shared.resolveConfiguredProject(ref, projects);
|
|
430
|
+
if (result.error) return result.error;
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
397
435
|
/**
|
|
398
436
|
* Aggregate archived work items from the central archive plus every project
|
|
399
437
|
* archive. Each item is tagged with `_source` (`'central'` or the project name)
|
|
@@ -425,12 +463,17 @@ function linkPullRequestForTracking({ url, title, project: projectName, autoObse
|
|
|
425
463
|
}
|
|
426
464
|
const projects = shared.getProjects(config);
|
|
427
465
|
const explicitProjectName = String(projectName || '').trim();
|
|
428
|
-
|
|
466
|
+
let targetProject = explicitProjectName ? findProjectByName(projects, explicitProjectName) : null;
|
|
429
467
|
if (explicitProjectName && !targetProject) {
|
|
430
468
|
const err = new Error(formatUnknownProjectError(explicitProjectName, projects));
|
|
431
469
|
err.statusCode = 400;
|
|
432
470
|
throw err;
|
|
433
471
|
}
|
|
472
|
+
if (!explicitProjectName) {
|
|
473
|
+
const prScope = shared.parsePrUrl(url)?.scope || '';
|
|
474
|
+
const matches = prScope ? projects.filter(p => shared.getProjectPrScope(p) === prScope) : [];
|
|
475
|
+
if (matches.length === 1) targetProject = matches[0];
|
|
476
|
+
}
|
|
434
477
|
const prPath = targetProject ? shared.projectPrPath(targetProject) : path.join(MINIONS_DIR, 'pull-requests.json');
|
|
435
478
|
|
|
436
479
|
const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
|
|
@@ -1814,7 +1857,8 @@ function _extractActionsJson(segment) {
|
|
|
1814
1857
|
return null;
|
|
1815
1858
|
}
|
|
1816
1859
|
|
|
1817
|
-
const CC_DISPATCH_ACTION_ALIASES = new Set(['fix', 'explore', 'review', 'test']);
|
|
1860
|
+
const CC_DISPATCH_ACTION_ALIASES = new Set(['fix', 'explore', 'review', 'test', 'implement', 'implement:large', 'ask', 'verify']);
|
|
1861
|
+
const CC_ACTION_INTENT_WORK_TYPES = new Set(['fix', 'implement', 'implement:large', 'explore', 'review', 'test', 'ask', 'verify']);
|
|
1818
1862
|
|
|
1819
1863
|
function normalizeCCAction(action) {
|
|
1820
1864
|
if (!action || typeof action !== 'object') return action;
|
|
@@ -1827,6 +1871,177 @@ function normalizeCCAction(action) {
|
|
|
1827
1871
|
return { ...action, type: 'dispatch', workType: action.workType || type };
|
|
1828
1872
|
}
|
|
1829
1873
|
|
|
1874
|
+
function _ccCleanIntentString(value, max = 500) {
|
|
1875
|
+
if (typeof value !== 'string') return '';
|
|
1876
|
+
let out = '';
|
|
1877
|
+
let lastWasSpace = true;
|
|
1878
|
+
for (const ch of value) {
|
|
1879
|
+
const isSpace = ch === '\0' || ch === ' ' || ch === '\n' || ch === '\r' || ch === '\t';
|
|
1880
|
+
if (isSpace) {
|
|
1881
|
+
if (!lastWasSpace) out += ' ';
|
|
1882
|
+
lastWasSpace = true;
|
|
1883
|
+
} else {
|
|
1884
|
+
out += ch;
|
|
1885
|
+
lastWasSpace = false;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return out.trim().slice(0, max);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function _ccNormalizeIntentMetadata(meta) {
|
|
1892
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return {};
|
|
1893
|
+
const out = {};
|
|
1894
|
+
for (const key of ['intent', 'type', 'title', 'description', 'priority', 'project', 'branchStrategy', 'branch_strategy']) {
|
|
1895
|
+
const value = _ccCleanIntentString(meta[key], key === 'description' ? 2000 : 300);
|
|
1896
|
+
if (value) out[key] = value;
|
|
1897
|
+
}
|
|
1898
|
+
if (Array.isArray(meta.agents)) {
|
|
1899
|
+
out.agents = meta.agents.map(a => _ccCleanIntentString(a, 80)).filter(Boolean);
|
|
1900
|
+
}
|
|
1901
|
+
if (Array.isArray(meta.projects)) {
|
|
1902
|
+
out.projects = meta.projects.map(p => _ccCleanIntentString(p, 120)).filter(Boolean);
|
|
1903
|
+
}
|
|
1904
|
+
if (meta.fanout === true) out.fanout = true;
|
|
1905
|
+
return out;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function _messageExplicitlyRequestsMinionsOrchestration(message) {
|
|
1909
|
+
const normalized = _normalizeIntentText(message);
|
|
1910
|
+
if (!normalized.trim()) return false;
|
|
1911
|
+
if (_messageHasDelegationIntent(message)) return true;
|
|
1912
|
+
if (_intentHasAnyToken(normalized, ['minions']) &&
|
|
1913
|
+
(_messageHasMediumLargeWorkIntent(message) || _messageRequestsPlanIntent(message))) return true;
|
|
1914
|
+
if (_intentHasAnyToken(normalized, ['agent', 'agents']) && _messageHasMediumLargeWorkIntent(message)) return true;
|
|
1915
|
+
return _intentHasVerbObject(normalized, ['create', 'make', 'draft', 'write'], ['plan']) &&
|
|
1916
|
+
_intentHasAnyToken(normalized, ['minion', 'minions']);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
function _ccInferMessageActionIntent(message, source = 'command-center') {
|
|
1920
|
+
const normalized = _normalizeIntentText(message);
|
|
1921
|
+
if (!normalized.trim()) return null;
|
|
1922
|
+
if (source === 'doc-chat' && !_messageExplicitlyRequestsMinionsOrchestration(message)) return null;
|
|
1923
|
+
if (_messageRequestsPlanIntent(message)) {
|
|
1924
|
+
return { kind: 'plan' };
|
|
1925
|
+
}
|
|
1926
|
+
if (!_messageHasDelegationIntent(message) && !_messageHasMediumLargeWorkIntent(message, { source })) return null;
|
|
1927
|
+
return { kind: 'work-item', workType: _inferDelegatedWorkType(message) };
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function _ccInferMetadataActionIntent(meta) {
|
|
1931
|
+
if (meta.intent === 'plan') return { kind: 'plan' };
|
|
1932
|
+
if (meta.intent === 'work-item' && CC_ACTION_INTENT_WORK_TYPES.has(String(meta.type || '').toLowerCase())) {
|
|
1933
|
+
return { kind: 'work-item', workType: String(meta.type).toLowerCase() };
|
|
1934
|
+
}
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function _ccActionIntentTargetText(message, intent) {
|
|
1939
|
+
const tokens = _intentTokens(_normalizeIntentText(_ccCleanIntentString(message, 500)));
|
|
1940
|
+
let start = 0;
|
|
1941
|
+
const skip = (terms) => {
|
|
1942
|
+
if (terms.includes(tokens[start])) {
|
|
1943
|
+
start++;
|
|
1944
|
+
return true;
|
|
1945
|
+
}
|
|
1946
|
+
return false;
|
|
1947
|
+
};
|
|
1948
|
+
skip(['please']);
|
|
1949
|
+
if (tokens[start] && tokens[start].startsWith('/')) start++;
|
|
1950
|
+
if (['dispatch', 'delegate', 'assign', 'queue', 'enqueue'].includes(tokens[start])) {
|
|
1951
|
+
start++;
|
|
1952
|
+
while (['a', 'an', 'the', 'work', 'item', 'task', 'agent', 'to'].includes(tokens[start])) start++;
|
|
1953
|
+
} else if (['create', 'open', 'file'].includes(tokens[start]) && tokens[start + 1] && ['a', 'an', 'the', 'work', 'item', 'task'].includes(tokens[start + 1])) {
|
|
1954
|
+
start++;
|
|
1955
|
+
while (['a', 'an', 'the', 'work', 'item', 'task', 'to', 'for'].includes(tokens[start])) start++;
|
|
1956
|
+
} else if (['have', 'ask', 'tell'].includes(tokens[start])) {
|
|
1957
|
+
start++;
|
|
1958
|
+
while (tokens[start] && !['to', 'fix', 'implement', 'review', 'test', 'explore', 'investigate', 'audit'].includes(tokens[start])) start++;
|
|
1959
|
+
if (tokens[start] === 'to') start++;
|
|
1960
|
+
}
|
|
1961
|
+
if (intent?.kind === 'plan') {
|
|
1962
|
+
while (['make', 'create', 'draft', 'write', 'design', 'plan', 'out', 'a', 'an', 'the', 'for', 'to', 'about', 'how'].includes(tokens[start])) start++;
|
|
1963
|
+
} else if (intent?.workType) {
|
|
1964
|
+
while (['fix', 'repair', 'resolve', 'patch', 'debug', 'implement', 'build', 'add', 'create', 'ship', 'review', 'inspect', 'audit', 'test', 'verify', 'validate', 'run', 'write', 'explore', 'investigate', 'research', 'analyze', 'analyse', 'understand', 'look', 'map', 'survey', 'a', 'an', 'the', 'for', 'to', 'of', 'on', 'about'].includes(tokens[start])) start++;
|
|
1965
|
+
}
|
|
1966
|
+
while (['for', 'to', 'of', 'on', 'about', 'a', 'an', 'the'].includes(tokens[start])) start++;
|
|
1967
|
+
return tokens.slice(start).join(' ');
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function _ccIntentHasConcreteTarget(message, metadataTitle, intent) {
|
|
1971
|
+
const raw = String(message || '');
|
|
1972
|
+
const normalized = _normalizeIntentText(raw);
|
|
1973
|
+
const tokens = _intentTokens(normalized);
|
|
1974
|
+
if (raw.includes('http://') || raw.includes('https://')) return true;
|
|
1975
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1976
|
+
if (['pr', 'issue'].includes(tokens[i]) && tokens[i + 1] && _intentTokenIsNumeric(tokens[i + 1])) return true;
|
|
1977
|
+
if (tokens[i] === 'pull' && tokens[i + 1] === 'request' && tokens[i + 2] && _intentTokenIsNumeric(tokens[i + 2])) return true;
|
|
1978
|
+
if (tokens[i] === 'w' && tokens[i + 1] && tokens[i + 1].length >= 2) return true;
|
|
1979
|
+
}
|
|
1980
|
+
const target = _ccActionIntentTargetText(metadataTitle || raw, intent).trim().toLowerCase();
|
|
1981
|
+
if (!target) return false;
|
|
1982
|
+
const targetTokens = _intentTokens(_normalizeIntentText(target));
|
|
1983
|
+
const generic = new Set(['this', 'that', 'it', 'these', 'those', 'same', 'above', 'here', 'there', 'one', 'thing', 'issue', 'bug', 'task', 'request', 'change', 'pr', 'pull', 'code', 'test', 'plan', 'doc', 'document', 'file']);
|
|
1984
|
+
if (targetTokens.length > 0 && targetTokens.every(token => generic.has(token))) return false;
|
|
1985
|
+
return target.length >= 3;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function _ccIntentTitle(message, metadataTitle, intent) {
|
|
1989
|
+
const metaTitle = _ccCleanIntentString(metadataTitle, 300);
|
|
1990
|
+
if (metaTitle && _ccIntentHasConcreteTarget(message, metaTitle, intent)) return metaTitle;
|
|
1991
|
+
const cleaned = _ccCleanIntentString(message, 300);
|
|
1992
|
+
if (!cleaned) return '';
|
|
1993
|
+
const title = intent?.kind === 'plan'
|
|
1994
|
+
? cleaned
|
|
1995
|
+
: _delegatedWorkTitle(cleaned, intent?.workType || 'ask');
|
|
1996
|
+
return title.charAt(0).toUpperCase() + title.slice(1);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
function _ccFallbackMissingTargetError(intent) {
|
|
2000
|
+
const label = intent?.kind === 'plan' ? 'plan' : (intent?.workType || 'dispatch');
|
|
2001
|
+
return `Missing target for ${label} request. Specify a concrete title, PR/work item, file, or feature to ${label}.`;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function _actionsWithIntentFallback(actions, opts = {}) {
|
|
2005
|
+
const { message = '', intentMetadata = null, source = 'command-center', filePath = null, title: docTitle = null, answerText = '', toolUses = [] } = opts;
|
|
2006
|
+
const existing = Array.isArray(actions) ? actions.map(normalizeCCAction) : [];
|
|
2007
|
+
if (_messageRequestsDirectHandling(message)) return existing.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
|
|
2008
|
+
if (existing.some(a => normalizeCCAction(a)?.type === 'dispatch')) return existing;
|
|
2009
|
+
if (existing.length > 0) return existing;
|
|
2010
|
+
const meta = _ccNormalizeIntentMetadata(intentMetadata);
|
|
2011
|
+
const messageIntent = _ccInferMessageActionIntent(message, source);
|
|
2012
|
+
const metadataIntent = source === 'doc-chat' ? null : _ccInferMetadataActionIntent(meta);
|
|
2013
|
+
const intent = messageIntent || metadataIntent;
|
|
2014
|
+
if (!intent) return _ensureDelegationForIntent(existing, { message, source, filePath, title: docTitle, answerText, toolUses });
|
|
2015
|
+
|
|
2016
|
+
const title = _ccIntentTitle(message, meta.title, intent);
|
|
2017
|
+
const hasTarget = _ccIntentHasConcreteTarget(message, meta.title, intent);
|
|
2018
|
+
const description = meta.description || _ccCleanIntentString(message, 2000);
|
|
2019
|
+
const common = {
|
|
2020
|
+
...(title ? { title } : {}),
|
|
2021
|
+
...(description ? { description } : {}),
|
|
2022
|
+
...(meta.priority ? { priority: meta.priority } : {}),
|
|
2023
|
+
...(meta.project ? { project: meta.project } : {}),
|
|
2024
|
+
...(Array.isArray(meta.agents) && meta.agents.length ? { agents: meta.agents } : {}),
|
|
2025
|
+
...(meta.fanout ? { scope: 'fan-out' } : {}),
|
|
2026
|
+
};
|
|
2027
|
+
if (intent.kind === 'plan') {
|
|
2028
|
+
const action = {
|
|
2029
|
+
type: 'plan',
|
|
2030
|
+
...common,
|
|
2031
|
+
branchStrategy: meta.branchStrategy || meta.branch_strategy || 'parallel',
|
|
2032
|
+
};
|
|
2033
|
+
if (!hasTarget) action._intentFallbackError = _ccFallbackMissingTargetError(intent);
|
|
2034
|
+
return [action];
|
|
2035
|
+
}
|
|
2036
|
+
const action = {
|
|
2037
|
+
type: 'dispatch',
|
|
2038
|
+
workType: intent.workType,
|
|
2039
|
+
...common,
|
|
2040
|
+
};
|
|
2041
|
+
if (!hasTarget) action._intentFallbackError = _ccFallbackMissingTargetError(intent);
|
|
2042
|
+
return [action];
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1830
2045
|
function parseCCActions(text) {
|
|
1831
2046
|
let actions = [];
|
|
1832
2047
|
let displayText = stripCCActionsForDisplay(text);
|
|
@@ -1882,6 +2097,261 @@ function parseCCActions(text) {
|
|
|
1882
2097
|
return result;
|
|
1883
2098
|
}
|
|
1884
2099
|
|
|
2100
|
+
const DELEGATION_ACTION_TERMS = ['dispatch', 'delegate', 'assign', 'queue', 'enqueue'];
|
|
2101
|
+
const DELEGATION_MINIONS_PHRASES = ['have minions', 'ask minions', 'tell minions', 'hand off', 'hand it off', 'hand this off', 'send to agent', 'send it to agent', 'send this to agent'];
|
|
2102
|
+
const MEDIUM_INVESTIGATION_TERMS = ['audit', 'investigate', 'research', 'explore', 'analyze', 'analyse'];
|
|
2103
|
+
const MEDIUM_INVESTIGATION_PHRASES = ['deep dive', 'root cause'];
|
|
2104
|
+
const MEDIUM_IMPLEMENT_TERMS = ['implement', 'fix', 'debug', 'repair', 'refactor', 'migrate', 'harden', 'redesign'];
|
|
2105
|
+
const MEDIUM_BUILD_VERBS = ['build', 'add', 'create'];
|
|
2106
|
+
const MEDIUM_BUILD_OBJECTS = ['feature', 'support', 'endpoint', 'api', 'ui', 'page', 'component', 'flow', 'integration', 'automation', 'test', 'tests', 'tool', 'command', 'dashboard', 'service', 'module'];
|
|
2107
|
+
const MEDIUM_REVIEW_TERMS = ['review', 'test', 'verify', 'validate'];
|
|
2108
|
+
const MEDIUM_SIZE_TERMS = ['medium', 'large', 'larger'];
|
|
2109
|
+
const MEDIUM_SIZE_PHRASES = ['cross cutting', 'multi file', 'multi step', 'multi stage', 'multi module'];
|
|
2110
|
+
const PLAN_INTENT_TERMS = ['plan'];
|
|
2111
|
+
const PLAN_CREATE_TERMS = ['make', 'create', 'draft', 'write', 'design'];
|
|
2112
|
+
const PLAN_CREATE_PHRASES = ['come up with'];
|
|
2113
|
+
const DOC_CHAT_DIRECT_DOC_TERMS = ['summarize', 'summary', 'quote', 'extract', 'rewrite', 'reword', 'copyedit', 'proofread', 'format', 'typo'];
|
|
2114
|
+
const DOC_CHAT_DOC_ACTION_TERMS = ['edit', 'update', 'change', 'review', 'check'];
|
|
2115
|
+
const DOC_CHAT_DOC_OBJECTS = ['document', 'doc', 'paragraph', 'section', 'selection', 'text', 'wording'];
|
|
2116
|
+
const DIRECT_QUICK_TERMS = ['quick', 'simple', 'small'];
|
|
2117
|
+
const DIRECT_QUICK_OBJECTS = ['question', 'answer', 'lookup', 'check'];
|
|
2118
|
+
const DIRECT_REPLY_TERMS = ['answer', 'respond', 'reply'];
|
|
2119
|
+
const DIRECT_REPLY_TARGETS = ['directly', 'inline', 'here'];
|
|
2120
|
+
const DIRECT_SELF_TERMS = ['do', 'handle', 'answer', 'fix', 'edit', 'update', 'change', 'review', 'check'];
|
|
2121
|
+
const DIRECT_SELF_TARGETS = ['yourself', 'directly'];
|
|
2122
|
+
const DIRECT_HANDLING_PHRASES = [
|
|
2123
|
+
'you do it yourself', 'you handle it yourself', 'you handle this yourself',
|
|
2124
|
+
'cc do it yourself', 'doc chat do it yourself', 'doc chat handle it yourself',
|
|
2125
|
+
'do not dispatch', 'dont dispatch', 'do not delegate', 'dont delegate',
|
|
2126
|
+
'do not assign', 'dont assign', 'do not queue', 'dont queue',
|
|
2127
|
+
'do not enqueue', 'dont enqueue', 'do not create work item', 'dont create work item',
|
|
2128
|
+
'no dispatch', 'no delegate', 'no delegation', 'no work item',
|
|
2129
|
+
'without dispatching', 'without delegating', 'without creating work item',
|
|
2130
|
+
];
|
|
2131
|
+
|
|
2132
|
+
function _isIntentWordChar(ch) {
|
|
2133
|
+
const code = ch.charCodeAt(0);
|
|
2134
|
+
return (code >= 48 && code <= 57) || (code >= 97 && code <= 122);
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function _normalizeIntentText(message) {
|
|
2138
|
+
const raw = String(message || '').toLowerCase();
|
|
2139
|
+
let out = ' ';
|
|
2140
|
+
let lastWasSpace = true;
|
|
2141
|
+
for (const ch of raw) {
|
|
2142
|
+
if (ch === '\'' || ch === '\u2019') continue;
|
|
2143
|
+
if (_isIntentWordChar(ch)) {
|
|
2144
|
+
out += ch;
|
|
2145
|
+
lastWasSpace = false;
|
|
2146
|
+
} else if (!lastWasSpace) {
|
|
2147
|
+
out += ' ';
|
|
2148
|
+
lastWasSpace = true;
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
return lastWasSpace ? out : out + ' ';
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
function _intentTokens(normalized) {
|
|
2155
|
+
const text = String(normalized || '').trim();
|
|
2156
|
+
return text ? text.split(' ') : [];
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
function _intentHasAnyToken(normalized, terms) {
|
|
2160
|
+
const tokens = new Set(_intentTokens(normalized));
|
|
2161
|
+
return terms.some(term => tokens.has(term));
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
function _intentHasPhrase(normalized, phrase) {
|
|
2165
|
+
return String(normalized || '').includes(` ${phrase} `);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
function _intentHasAnyPhrase(normalized, phrases) {
|
|
2169
|
+
return phrases.some(phrase => _intentHasPhrase(normalized, phrase));
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function _intentTokenIsNumeric(token) {
|
|
2173
|
+
if (!token) return false;
|
|
2174
|
+
for (const ch of String(token)) {
|
|
2175
|
+
const code = ch.charCodeAt(0);
|
|
2176
|
+
if (code < 48 || code > 57) return false;
|
|
2177
|
+
}
|
|
2178
|
+
return true;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function _intentHasVerbObject(normalized, verbs, objects) {
|
|
2182
|
+
const verbSet = new Set(verbs);
|
|
2183
|
+
const objectSet = new Set(objects);
|
|
2184
|
+
const filler = new Set(['a', 'an', 'the', 'this', 'that', 'it', 'new', 'proper', 'some']);
|
|
2185
|
+
const tokens = _intentTokens(normalized);
|
|
2186
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2187
|
+
if (!verbSet.has(tokens[i])) continue;
|
|
2188
|
+
for (let j = i + 1; j < tokens.length && j <= i + 4; j++) {
|
|
2189
|
+
if (filler.has(tokens[j])) continue;
|
|
2190
|
+
if (objectSet.has(tokens[j])) return true;
|
|
2191
|
+
break;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return false;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
function _messageRequestsPlanIntent(message) {
|
|
2198
|
+
const normalized = _normalizeIntentText(message);
|
|
2199
|
+
const tokens = _intentTokens(normalized);
|
|
2200
|
+
if (!tokens.length) return false;
|
|
2201
|
+
if (tokens[0] === 'plan') return true;
|
|
2202
|
+
if (_intentHasAnyPhrase(normalized, PLAN_CREATE_PHRASES) && _intentHasAnyToken(normalized, PLAN_INTENT_TERMS)) return true;
|
|
2203
|
+
if (_intentHasVerbObject(normalized, PLAN_CREATE_TERMS, PLAN_INTENT_TERMS)) return true;
|
|
2204
|
+
return _intentHasAnyToken(normalized, PLAN_INTENT_TERMS) &&
|
|
2205
|
+
_intentHasAnyToken(normalized, ['this', 'that', 'it', 'for', 'how', 'out']);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function _messageHasDelegationIntent(message) {
|
|
2209
|
+
const normalized = _normalizeIntentText(message);
|
|
2210
|
+
if (!normalized.trim()) return false;
|
|
2211
|
+
if (_intentHasAnyToken(normalized, DELEGATION_ACTION_TERMS)) return true;
|
|
2212
|
+
if (_intentHasPhrase(normalized, 'work item') && _intentHasAnyToken(normalized, ['create', 'open', 'add'])) return true;
|
|
2213
|
+
return _intentHasAnyPhrase(normalized, DELEGATION_MINIONS_PHRASES);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
function _messageRequestsDirectHandling(message) {
|
|
2217
|
+
const normalized = _normalizeIntentText(message);
|
|
2218
|
+
if (!normalized.trim()) return false;
|
|
2219
|
+
if (_intentHasAnyPhrase(normalized, DIRECT_HANDLING_PHRASES)) return true;
|
|
2220
|
+
if (_intentHasAnyToken(normalized, DIRECT_QUICK_TERMS) && _intentHasAnyToken(normalized, DIRECT_QUICK_OBJECTS)) return true;
|
|
2221
|
+
if (_intentHasVerbObject(normalized, DIRECT_REPLY_TERMS, DIRECT_REPLY_TARGETS)) return true;
|
|
2222
|
+
return _intentHasVerbObject(normalized, DIRECT_SELF_TERMS, DIRECT_SELF_TARGETS);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
function _messageIsSmallDocOnlyRequest(normalized) {
|
|
2226
|
+
if (_intentHasAnyToken(normalized, DOC_CHAT_DIRECT_DOC_TERMS)) return true;
|
|
2227
|
+
return _intentHasVerbObject(normalized, DOC_CHAT_DOC_ACTION_TERMS, DOC_CHAT_DOC_OBJECTS);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function _messageHasMediumLargeWorkIntent(message, { source = 'command-center' } = {}) {
|
|
2231
|
+
const normalized = _normalizeIntentText(message);
|
|
2232
|
+
if (!normalized.trim()) return false;
|
|
2233
|
+
if (_messageRequestsDirectHandling(message)) return false;
|
|
2234
|
+
if (source === 'doc-chat' && _messageIsSmallDocOnlyRequest(normalized)) return false;
|
|
2235
|
+
return _intentHasAnyToken(normalized, MEDIUM_INVESTIGATION_TERMS) ||
|
|
2236
|
+
_intentHasAnyPhrase(normalized, MEDIUM_INVESTIGATION_PHRASES) ||
|
|
2237
|
+
_intentHasAnyToken(normalized, MEDIUM_IMPLEMENT_TERMS) ||
|
|
2238
|
+
_intentHasVerbObject(normalized, MEDIUM_BUILD_VERBS, MEDIUM_BUILD_OBJECTS) ||
|
|
2239
|
+
_intentHasAnyToken(normalized, MEDIUM_REVIEW_TERMS) ||
|
|
2240
|
+
_intentHasAnyToken(normalized, MEDIUM_SIZE_TERMS) ||
|
|
2241
|
+
_intentHasAnyPhrase(normalized, MEDIUM_SIZE_PHRASES);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function _inferDelegatedWorkType(message) {
|
|
2245
|
+
const normalized = _normalizeIntentText(message);
|
|
2246
|
+
if (_intentHasAnyToken(normalized, ['test', 'verify', 'validate']) || _intentHasPhrase(normalized, 'build and test')) return 'test';
|
|
2247
|
+
if (_intentHasAnyToken(normalized, ['review']) || _intentHasPhrase(normalized, 'code review')) return 'review';
|
|
2248
|
+
if (_intentHasAnyToken(normalized, ['fix', 'debug', 'repair', 'bug', 'broken', 'failing', 'failure', 'regression']) || _intentHasPhrase(normalized, 'root cause')) return 'fix';
|
|
2249
|
+
if (_intentHasAnyToken(normalized, ['implement', 'refactor', 'migrate', 'harden', 'redesign']) ||
|
|
2250
|
+
_intentHasVerbObject(normalized, MEDIUM_BUILD_VERBS, MEDIUM_BUILD_OBJECTS)) return 'implement';
|
|
2251
|
+
if (_intentHasAnyToken(normalized, MEDIUM_INVESTIGATION_TERMS) || _intentHasAnyPhrase(normalized, MEDIUM_INVESTIGATION_PHRASES)) return 'explore';
|
|
2252
|
+
return 'ask';
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
function _collapseWhitespace(text) {
|
|
2256
|
+
let out = '';
|
|
2257
|
+
let lastWasSpace = true;
|
|
2258
|
+
for (const ch of String(text || '')) {
|
|
2259
|
+
const isSpace = ch === ' ' || ch === '\n' || ch === '\r' || ch === '\t';
|
|
2260
|
+
if (isSpace) {
|
|
2261
|
+
if (!lastWasSpace) out += ' ';
|
|
2262
|
+
lastWasSpace = true;
|
|
2263
|
+
} else {
|
|
2264
|
+
out += ch;
|
|
2265
|
+
lastWasSpace = false;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
return out.trim();
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
function _stripEdgeQuotes(text) {
|
|
2272
|
+
const quotes = new Set(['"', "'", '`']);
|
|
2273
|
+
let start = 0;
|
|
2274
|
+
let end = String(text || '').length;
|
|
2275
|
+
while (start < end && quotes.has(text[start])) start++;
|
|
2276
|
+
while (end > start && quotes.has(text[end - 1])) end--;
|
|
2277
|
+
return text.slice(start, end);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
function _delegatedWorkTitle(message, workType) {
|
|
2281
|
+
const compact = _stripEdgeQuotes(_collapseWhitespace(String(message || '')));
|
|
2282
|
+
const trimmed = compact.length > 90 ? compact.slice(0, 87).trimEnd() + '...' : compact;
|
|
2283
|
+
const fallback = {
|
|
2284
|
+
fix: 'Fix requested issue',
|
|
2285
|
+
review: 'Review requested work',
|
|
2286
|
+
test: 'Test requested work',
|
|
2287
|
+
implement: 'Implement requested work',
|
|
2288
|
+
'implement:large': 'Implement requested work',
|
|
2289
|
+
explore: 'Investigate requested work',
|
|
2290
|
+
ask: 'Answer requested query',
|
|
2291
|
+
verify: 'Verify requested work',
|
|
2292
|
+
}[workType] || 'Handle requested work';
|
|
2293
|
+
if (!trimmed) return fallback;
|
|
2294
|
+
const prefix = {
|
|
2295
|
+
fix: 'Fix',
|
|
2296
|
+
review: 'Review',
|
|
2297
|
+
test: 'Test',
|
|
2298
|
+
implement: 'Implement',
|
|
2299
|
+
'implement:large': 'Implement',
|
|
2300
|
+
explore: 'Investigate',
|
|
2301
|
+
ask: 'Answer',
|
|
2302
|
+
verify: 'Verify',
|
|
2303
|
+
}[workType] || 'Handle';
|
|
2304
|
+
const lower = trimmed.toLowerCase();
|
|
2305
|
+
const prefixLower = prefix.toLowerCase();
|
|
2306
|
+
if (lower === prefixLower || lower.startsWith(prefixLower + ' ')) return trimmed;
|
|
2307
|
+
return `${prefix}: ${trimmed}`;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function _priorityFromDelegationMessage(message) {
|
|
2311
|
+
const normalized = _normalizeIntentText(message);
|
|
2312
|
+
if (_intentHasAnyToken(normalized, ['urgent', 'asap', 'critical', 'blocker', 'p0', 'p1']) || _intentHasPhrase(normalized, 'high priority')) return 'high';
|
|
2313
|
+
if (_intentHasPhrase(normalized, 'low priority') || _intentHasPhrase(normalized, 'when you can') || _intentHasAnyToken(normalized, ['whenever'])) return 'low';
|
|
2314
|
+
return 'medium';
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function _inferDelegationActionFromMessage(message, { source = 'command-center', filePath = null, title = null, answerText = '', toolUses = [] } = {}) {
|
|
2318
|
+
if (_messageRequestsDirectHandling(message)) return null;
|
|
2319
|
+
const explicit = _messageHasDelegationIntent(message);
|
|
2320
|
+
const mediumLarge = _messageHasMediumLargeWorkIntent(message, { source });
|
|
2321
|
+
const toolCount = Array.isArray(toolUses) ? toolUses.length : 0;
|
|
2322
|
+
if (!explicit && !mediumLarge && toolCount < 4) return null;
|
|
2323
|
+
|
|
2324
|
+
const workType = _inferDelegatedWorkType(message);
|
|
2325
|
+
const inferredProject = source === 'doc-chat' && filePath && typeof _inferDocChatProject === 'function'
|
|
2326
|
+
? _inferDocChatProject(filePath)
|
|
2327
|
+
: null;
|
|
2328
|
+
const context = [];
|
|
2329
|
+
context.push(`${source === 'doc-chat' ? 'Doc-chat' : 'Command Center'} detected ${explicit ? 'an explicit dispatch/delegation request' : (toolCount >= 4 ? 'a medium-sized tool-using request' : 'a medium/large work request')} and routed it through the engine.`);
|
|
2330
|
+
if (filePath) context.push(`Document: ${filePath}`);
|
|
2331
|
+
if (title && title !== filePath) context.push(`Title: ${title}`);
|
|
2332
|
+
if (answerText && String(answerText).trim()) context.push(`Assistant draft before delegation:\n${String(answerText).trim().slice(0, 1200)}`);
|
|
2333
|
+
context.push(`Original request:\n${String(message || '').trim()}`);
|
|
2334
|
+
|
|
2335
|
+
return {
|
|
2336
|
+
type: 'dispatch',
|
|
2337
|
+
title: _delegatedWorkTitle(message, workType),
|
|
2338
|
+
workType,
|
|
2339
|
+
priority: _priorityFromDelegationMessage(message),
|
|
2340
|
+
description: context.join('\n\n'),
|
|
2341
|
+
...(inferredProject ? { project: inferredProject } : {}),
|
|
2342
|
+
_autoDelegated: true,
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
function _ensureDelegationForIntent(actions, opts = {}) {
|
|
2347
|
+
const list = Array.isArray(actions) ? actions.map(normalizeCCAction) : [];
|
|
2348
|
+
if (_messageRequestsDirectHandling(opts.message)) return list.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
|
|
2349
|
+
if (list.some(a => normalizeCCAction(a)?.type === 'dispatch')) return list;
|
|
2350
|
+
if (list.length > 0) return list;
|
|
2351
|
+
const inferred = _inferDelegationActionFromMessage(opts.message, opts);
|
|
2352
|
+
return inferred ? [...list, inferred] : list;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
1885
2355
|
function stripCCActionSyntax(text) {
|
|
1886
2356
|
if (!text) return '';
|
|
1887
2357
|
let displayText = text;
|
|
@@ -2258,10 +2728,13 @@ function createPipelineFromAction(action) {
|
|
|
2258
2728
|
// before `try` to avoid filling `results` with cryptic per-handler error messages.
|
|
2259
2729
|
function _ccValidateAction(action) {
|
|
2260
2730
|
if (!action || typeof action !== 'object' || !action.type) return 'action is missing required field: type';
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2731
|
+
const normalized = normalizeCCAction(action);
|
|
2732
|
+
switch (normalized.type) {
|
|
2733
|
+
case 'dispatch':
|
|
2734
|
+
if (!normalized.title || typeof normalized.title !== 'string' || !normalized.title.trim()) return `${action.type} action missing required field: title`;
|
|
2264
2735
|
return null;
|
|
2736
|
+
case 'implement':
|
|
2737
|
+
return 'Unsupported action type "implement"; use type="dispatch" with workType="implement".';
|
|
2265
2738
|
case 'build-and-test':
|
|
2266
2739
|
if (!action.pr) return 'build-and-test action missing required field: pr';
|
|
2267
2740
|
return null;
|
|
@@ -2586,6 +3059,15 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
|
|
|
2586
3059
|
const results = [];
|
|
2587
3060
|
for (const rawAction of actions) {
|
|
2588
3061
|
const action = normalizeCCAction(rawAction);
|
|
3062
|
+
if (action?._intentFallbackError) {
|
|
3063
|
+
results.push({
|
|
3064
|
+
type: action.type || 'dispatch',
|
|
3065
|
+
...(action.workType ? { workType: action.workType } : {}),
|
|
3066
|
+
error: action._intentFallbackError,
|
|
3067
|
+
missingTarget: true,
|
|
3068
|
+
});
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
2589
3071
|
const validationError = _ccValidateAction(action);
|
|
2590
3072
|
if (validationError) {
|
|
2591
3073
|
results.push({ type: action?.type || 'unknown', error: validationError });
|
|
@@ -2593,7 +3075,7 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
|
|
|
2593
3075
|
}
|
|
2594
3076
|
try {
|
|
2595
3077
|
switch (action.type) {
|
|
2596
|
-
case 'dispatch':
|
|
3078
|
+
case 'dispatch': {
|
|
2597
3079
|
const workType = routing.normalizeWorkType(action.workType || (action.type !== 'dispatch' ? action.type : WORK_TYPE.IMPLEMENT), WORK_TYPE.IMPLEMENT);
|
|
2598
3080
|
const id = 'W-' + shared.uid();
|
|
2599
3081
|
const project = action.project || '';
|
|
@@ -2660,7 +3142,7 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
|
|
|
2660
3142
|
|
|
2661
3143
|
// Issue #1772: CC review/explore/test are human-initiated one-offs.
|
|
2662
3144
|
// Mark oneShot so any discovered PR is tagged _contextOnly (skips eval loop).
|
|
2663
|
-
const ccOneShotTypes = new Set([
|
|
3145
|
+
const ccOneShotTypes = new Set([WORK_TYPE.REVIEW, WORK_TYPE.EXPLORE, WORK_TYPE.TEST, WORK_TYPE.ASK, WORK_TYPE.VERIFY]);
|
|
2664
3146
|
const isOneShot = action.oneShot === true || (action.oneShot !== false && ccOneShotTypes.has(workType));
|
|
2665
3147
|
const item = {
|
|
2666
3148
|
id, title: action.title.trim(), type: workType,
|
|
@@ -2887,7 +3369,7 @@ async function executeDocChatActions(actions, { filePath = null } = {}) {
|
|
|
2887
3369
|
return executeCCActions(actions, { source: 'doc-chat', inferredProject: _inferDocChatProject(filePath) });
|
|
2888
3370
|
}
|
|
2889
3371
|
|
|
2890
|
-
const DOC_CHAT_WORK_ITEM_ACTION_TYPES = new Set(['dispatch', 'fix', 'implement', 'explore', 'review', 'test']);
|
|
3372
|
+
const DOC_CHAT_WORK_ITEM_ACTION_TYPES = new Set(['dispatch', 'fix', 'implement', 'implement:large', 'explore', 'review', 'test', 'ask', 'verify']);
|
|
2891
3373
|
|
|
2892
3374
|
function _buildDocChatActionFeedback(actions, actionResults) {
|
|
2893
3375
|
if (!Array.isArray(actions) || !Array.isArray(actionResults)) return [];
|
|
@@ -3688,8 +4170,9 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
3688
4170
|
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
3689
4171
|
// Match Command Center's full tool surface and turn budget so doc-chat
|
|
3690
4172
|
// can take action (read/write/edit/dispatch) instead of being limited
|
|
3691
|
-
// to Q&A. The doc-chat sysprompt
|
|
3692
|
-
//
|
|
4173
|
+
// to Q&A. The doc-chat sysprompt scopes orchestration to human message
|
|
4174
|
+
// intent (explicit dispatch or medium/larger work), and ---DOCUMENT---
|
|
4175
|
+
// remains the only whole-file document edit channel.
|
|
3693
4176
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
3694
4177
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
3695
4178
|
transcript,
|
|
@@ -4583,6 +5066,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
4583
5066
|
try {
|
|
4584
5067
|
const body = await readBody(req);
|
|
4585
5068
|
if (!body.title || !body.title.trim()) return jsonReply(res, 400, { error: 'title is required' });
|
|
5069
|
+
const target = resolveWorkItemsCreateTarget(body.project);
|
|
5070
|
+
if (target.error) return jsonReply(res, 400, { error: target.error });
|
|
4586
5071
|
// Write as a work item with type 'plan' — user must explicitly execute plan-to-prd after reviewing
|
|
4587
5072
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
4588
5073
|
const id = 'W-' + shared.uid();
|
|
@@ -4592,7 +5077,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4592
5077
|
status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
|
|
4593
5078
|
branchStrategy: body.branch_strategy || body.branchStrategy || 'parallel',
|
|
4594
5079
|
};
|
|
4595
|
-
if (
|
|
5080
|
+
if (target.project) item.project = target.project.name;
|
|
4596
5081
|
if (body.agent) item.agent = body.agent;
|
|
4597
5082
|
mutateWorkItems(wiPath, items => { items.push(item); });
|
|
4598
5083
|
return jsonReply(res, 200, { ok: true, id, agent: body.agent || '' });
|
|
@@ -4603,6 +5088,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
4603
5088
|
try {
|
|
4604
5089
|
const body = await readBody(req);
|
|
4605
5090
|
if (!body.name || !body.name.trim()) return jsonReply(res, 400, { error: 'name is required' });
|
|
5091
|
+
const target = resolveWorkItemsCreateTarget(body.project);
|
|
5092
|
+
if (target.error) return jsonReply(res, 400, { error: target.error });
|
|
4606
5093
|
|
|
4607
5094
|
if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
|
|
4608
5095
|
|
|
@@ -4610,7 +5097,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4610
5097
|
const planFile = 'manual-' + shared.uid() + '.json';
|
|
4611
5098
|
const plan = {
|
|
4612
5099
|
version: 'manual-' + new Date().toISOString().slice(0, 10),
|
|
4613
|
-
project:
|
|
5100
|
+
project: target.project?.name || (PROJECTS.length > 0 ? PROJECTS[0].name : 'Unknown'),
|
|
4614
5101
|
generated_by: 'dashboard',
|
|
4615
5102
|
generated_at: new Date().toISOString().slice(0, 10),
|
|
4616
5103
|
plan_summary: body.name,
|
|
@@ -5913,7 +6400,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5913
6400
|
}
|
|
5914
6401
|
}
|
|
5915
6402
|
|
|
5916
|
-
|
|
6403
|
+
let { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCall({
|
|
5917
6404
|
message: body.message, document: currentContent, title: body.title,
|
|
5918
6405
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
5919
6406
|
model: body.model || undefined,
|
|
@@ -5921,14 +6408,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5921
6408
|
transcript: body.transcript,
|
|
5922
6409
|
onAbortReady: (abort) => { _docAbort = abort; },
|
|
5923
6410
|
});
|
|
5924
|
-
const
|
|
5925
|
-
|
|
6411
|
+
const delegatedActions = _ensureDelegationForIntent(actions, {
|
|
6412
|
+
message: body.message, source: 'doc-chat', filePath: body.filePath, title: body.title, answerText: answer, toolUses,
|
|
6413
|
+
});
|
|
6414
|
+
const actionResults = await executeDocChatActions(delegatedActions, { filePath: body.filePath });
|
|
6415
|
+
const actionFeedback = _buildDocChatActionFeedback(delegatedActions, actionResults);
|
|
5926
6416
|
const finalize = _finalizeDocChatEdit({
|
|
5927
6417
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
5928
6418
|
originalContent: currentContent, delimiterContent: content,
|
|
5929
6419
|
});
|
|
5930
6420
|
const payload = _buildDocChatResponsePayload({
|
|
5931
|
-
answer, actions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6421
|
+
answer, actions: delegatedActions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
5932
6422
|
});
|
|
5933
6423
|
_docDone = true;
|
|
5934
6424
|
return jsonReply(res, 200, payload);
|
|
@@ -6005,7 +6495,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6005
6495
|
|
|
6006
6496
|
try {
|
|
6007
6497
|
|
|
6008
|
-
|
|
6498
|
+
let { answer, content, actions, actionParseError, partial, warning, toolUses, error: ccError } = await ccDocCallStreaming({
|
|
6009
6499
|
message: body.message, document: currentContent, title: body.title,
|
|
6010
6500
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
6011
6501
|
model: body.model || undefined,
|
|
@@ -6016,14 +6506,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6016
6506
|
onToolUse: (name, input) => { writeDocEvent({ type: 'tool', name, input: _lightToolInput(input) }); },
|
|
6017
6507
|
onRetry: (attempt) => { writeDocEvent({ type: 'progress', attempt }); },
|
|
6018
6508
|
});
|
|
6019
|
-
const
|
|
6020
|
-
|
|
6509
|
+
const delegatedActions = _ensureDelegationForIntent(actions, {
|
|
6510
|
+
message: body.message, source: 'doc-chat', filePath: body.filePath, title: body.title, answerText: answer, toolUses,
|
|
6511
|
+
});
|
|
6512
|
+
const actionResults = await executeDocChatActions(delegatedActions, { filePath: body.filePath });
|
|
6513
|
+
const actionFeedback = _buildDocChatActionFeedback(delegatedActions, actionResults);
|
|
6021
6514
|
const finalize = _finalizeDocChatEdit({
|
|
6022
6515
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
6023
6516
|
originalContent: currentContent, delimiterContent: content,
|
|
6024
6517
|
});
|
|
6025
6518
|
const payload = _buildDocChatResponsePayload({
|
|
6026
|
-
answer, actions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6519
|
+
answer, actions: delegatedActions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6027
6520
|
});
|
|
6028
6521
|
const { answer: finalAnswer, ...donePayload } = payload;
|
|
6029
6522
|
writeDocEvent({
|
|
@@ -6544,8 +7037,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6544
7037
|
});
|
|
6545
7038
|
}
|
|
6546
7039
|
|
|
6547
|
-
const parsed = parseCCActions(result.text);
|
|
6548
7040
|
const toolUses = Array.isArray(result.toolUses) ? result.toolUses : _extractToolUsesFromRaw(result.raw);
|
|
7041
|
+
const parsed = parseCCActions(result.text);
|
|
6549
7042
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
6550
7043
|
const _loopWatch = _detectLoopInvocation(parsed.text, parsed.actions, toolUses, body.message);
|
|
6551
7044
|
if (_loopWatch) {
|
|
@@ -6553,7 +7046,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6553
7046
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
6554
7047
|
try { shared.log('warn', '/loop invocation detected in CC response — auto-converted to create-watch'); } catch {}
|
|
6555
7048
|
}
|
|
6556
|
-
parsed.actions =
|
|
7049
|
+
parsed.actions = _actionsWithIntentFallback(
|
|
7050
|
+
_filterImplicitPostDispatchActions(parsed.actions, body.message),
|
|
7051
|
+
{ message: body.message, intentMetadata: body.intentMetadata, source: 'command-center', answerText: parsed.text, toolUses }
|
|
7052
|
+
);
|
|
6557
7053
|
if (parsed.actions.length > 0) {
|
|
6558
7054
|
parsed.actionResults = await executeCCActions(parsed.actions);
|
|
6559
7055
|
}
|
|
@@ -6881,7 +7377,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6881
7377
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
6882
7378
|
try { shared.log('warn', '/loop invocation detected in CC response — auto-converted to create-watch'); } catch {}
|
|
6883
7379
|
}
|
|
6884
|
-
actions =
|
|
7380
|
+
actions = _actionsWithIntentFallback(
|
|
7381
|
+
_filterImplicitPostDispatchActions(actions, body.message),
|
|
7382
|
+
{ message: body.message, intentMetadata: body.intentMetadata, source: 'command-center', answerText: displayText, toolUses }
|
|
7383
|
+
);
|
|
6885
7384
|
let actionResults;
|
|
6886
7385
|
if (actions.length > 0) {
|
|
6887
7386
|
actionResults = await executeCCActions(actions);
|
|
@@ -6945,6 +7444,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6945
7444
|
const body = await readBody(req);
|
|
6946
7445
|
let { id, cron, title, type, project, agent, description, priority, enabled } = body;
|
|
6947
7446
|
if (!cron || !title) return jsonReply(res, 400, { error: 'cron and title are required' });
|
|
7447
|
+
const projectTarget = shared.resolveConfiguredProject(project, PROJECTS);
|
|
7448
|
+
if (projectTarget.error) return jsonReply(res, 400, { error: projectTarget.error });
|
|
7449
|
+
project = projectTarget.project?.name || null;
|
|
6948
7450
|
|
|
6949
7451
|
// Auto-generate ID from title if not provided
|
|
6950
7452
|
if (!id) {
|
|
@@ -6977,8 +7479,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6977
7479
|
|
|
6978
7480
|
async function handleSchedulesUpdate(req, res) {
|
|
6979
7481
|
const body = await readBody(req);
|
|
6980
|
-
|
|
7482
|
+
let { id, cron, title, type, project, agent, description, priority, enabled } = body;
|
|
6981
7483
|
if (!id) return jsonReply(res, 400, { error: 'id required' });
|
|
7484
|
+
if (project !== undefined) {
|
|
7485
|
+
const projectTarget = shared.resolveConfiguredProject(project, PROJECTS);
|
|
7486
|
+
if (projectTarget.error) return jsonReply(res, 400, { error: projectTarget.error });
|
|
7487
|
+
project = projectTarget.project?.name || null;
|
|
7488
|
+
}
|
|
6982
7489
|
|
|
6983
7490
|
let missingSchedules = false;
|
|
6984
7491
|
let sched = null;
|
|
@@ -7039,6 +7546,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7039
7546
|
reloadConfig();
|
|
7040
7547
|
const sched = (CONFIG.schedules || []).find(s => s.id === id);
|
|
7041
7548
|
if (!sched) return jsonReply(res, 404, { error: 'Schedule not found' });
|
|
7549
|
+
if (sched.project) {
|
|
7550
|
+
const projectTarget = shared.resolveConfiguredProject(sched.project, PROJECTS);
|
|
7551
|
+
if (projectTarget.error) return jsonReply(res, 400, { error: projectTarget.error });
|
|
7552
|
+
sched.project = projectTarget.project.name;
|
|
7553
|
+
}
|
|
7042
7554
|
|
|
7043
7555
|
const schedulerMod = require('./engine/scheduler');
|
|
7044
7556
|
let item;
|
|
@@ -8146,8 +8658,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8146
8658
|
const { savePipeline, getPipeline } = require('./engine/pipeline');
|
|
8147
8659
|
if (getPipeline(body.id)) return jsonReply(res, 409, { error: 'Pipeline already exists' });
|
|
8148
8660
|
const pipeline = { id: body.id, title: body.title, stages: body.stages, trigger: body.trigger || {}, enabled: body.enabled !== false };
|
|
8661
|
+
if (body.project !== undefined) pipeline.project = body.project;
|
|
8662
|
+
if (Array.isArray(body.projects)) pipeline.projects = body.projects;
|
|
8149
8663
|
if (body.stopWhen) pipeline.stopWhen = body.stopWhen;
|
|
8150
8664
|
if (Array.isArray(body.monitoredResources) && body.monitoredResources.length > 0) pipeline.monitoredResources = body.monitoredResources;
|
|
8665
|
+
const projectError = validatePipelineProjects(pipeline);
|
|
8666
|
+
if (projectError) return jsonReply(res, 400, { error: projectError });
|
|
8151
8667
|
savePipeline(pipeline);
|
|
8152
8668
|
invalidateStatusCache();
|
|
8153
8669
|
return jsonReply(res, 200, { ok: true, id: pipeline.id });
|
|
@@ -8162,8 +8678,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8162
8678
|
if (body.stages !== undefined) pipeline.stages = body.stages;
|
|
8163
8679
|
if (body.trigger !== undefined) pipeline.trigger = body.trigger;
|
|
8164
8680
|
if (body.enabled !== undefined) pipeline.enabled = body.enabled;
|
|
8681
|
+
if (body.project !== undefined) pipeline.project = body.project;
|
|
8682
|
+
if (body.projects !== undefined) pipeline.projects = body.projects;
|
|
8165
8683
|
if (body.monitoredResources !== undefined) pipeline.monitoredResources = body.monitoredResources;
|
|
8166
8684
|
if (body.stopWhen !== undefined) pipeline.stopWhen = body.stopWhen;
|
|
8685
|
+
const projectError = validatePipelineProjects(pipeline);
|
|
8686
|
+
if (projectError) return jsonReply(res, 400, { error: projectError });
|
|
8167
8687
|
savePipeline(pipeline);
|
|
8168
8688
|
invalidateStatusCache();
|
|
8169
8689
|
return jsonReply(res, 200, { ok: true });
|
|
@@ -8507,10 +9027,16 @@ module.exports = {
|
|
|
8507
9027
|
_shouldSuppressDocChatPostPatchError,
|
|
8508
9028
|
_buildDocChatResponsePayload,
|
|
8509
9029
|
_inferDocChatProject,
|
|
9030
|
+
_messageHasDelegationIntent,
|
|
9031
|
+
_messageRequestsDirectHandling,
|
|
9032
|
+
_messageHasMediumLargeWorkIntent,
|
|
9033
|
+
_inferDelegationActionFromMessage,
|
|
9034
|
+
_ensureDelegationForIntent,
|
|
8510
9035
|
_linkPullRequestForTracking: linkPullRequestForTracking,
|
|
8511
9036
|
_resolveSkillReadPath,
|
|
8512
9037
|
DOC_CHAT_DOCUMENT_DELIMITER,
|
|
8513
9038
|
_ccValidateAction,
|
|
9039
|
+
_actionsWithIntentFallback,
|
|
8514
9040
|
_messageExplicitlyRequestsMonitoring,
|
|
8515
9041
|
_filterImplicitPostDispatchActions,
|
|
8516
9042
|
_findDuplicateWorkItemCreate: findDuplicateWorkItemCreate,
|