@yemi33/minions 0.1.1809 → 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 +7 -2
- package/dashboard.js +447 -101
- 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,8 +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']);
|
|
1818
|
-
const CC_ACTION_INTENT_WORK_TYPES = new Set(['fix', 'implement', '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']);
|
|
1819
1862
|
|
|
1820
1863
|
function normalizeCCAction(action) {
|
|
1821
1864
|
if (!action || typeof action !== 'object') return action;
|
|
@@ -1830,7 +1873,19 @@ function normalizeCCAction(action) {
|
|
|
1830
1873
|
|
|
1831
1874
|
function _ccCleanIntentString(value, max = 500) {
|
|
1832
1875
|
if (typeof value !== 'string') return '';
|
|
1833
|
-
|
|
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);
|
|
1834
1889
|
}
|
|
1835
1890
|
|
|
1836
1891
|
function _ccNormalizeIntentMetadata(meta) {
|
|
@@ -1851,36 +1906,25 @@ function _ccNormalizeIntentMetadata(meta) {
|
|
|
1851
1906
|
}
|
|
1852
1907
|
|
|
1853
1908
|
function _messageExplicitlyRequestsMinionsOrchestration(message) {
|
|
1854
|
-
const
|
|
1855
|
-
if (!
|
|
1856
|
-
return
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
].some(pattern => pattern.test(text));
|
|
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']);
|
|
1863
1917
|
}
|
|
1864
1918
|
|
|
1865
1919
|
function _ccInferMessageActionIntent(message, source = 'command-center') {
|
|
1866
|
-
const
|
|
1867
|
-
|
|
1868
|
-
if (!
|
|
1869
|
-
if (
|
|
1870
|
-
if (/^\/plan\b/.test(lower) || /\b(?:make|create|draft|write|design|come up with)\s+(?:a\s+)?plan\b/.test(lower) || /\bplan\s+(?:this|that|it|for|how|out)\b/.test(lower)) {
|
|
1920
|
+
const normalized = _normalizeIntentText(message);
|
|
1921
|
+
if (!normalized.trim()) return null;
|
|
1922
|
+
if (source === 'doc-chat' && !_messageExplicitlyRequestsMinionsOrchestration(message)) return null;
|
|
1923
|
+
if (_messageRequestsPlanIntent(message)) {
|
|
1871
1924
|
return { kind: 'plan' };
|
|
1872
1925
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
['implement', /\b(?:implement|build|add|create|ship)\b/],
|
|
1876
|
-
['review', /\b(?:review|code review|inspect|audit)\b/],
|
|
1877
|
-
['test', /\b(?:test|verify|validate|run tests?|write tests?|add tests?|coverage|build)\b/],
|
|
1878
|
-
['explore', /\b(?:explore|investigate|research|analyze|understand|look into|map out|survey)\b/],
|
|
1879
|
-
];
|
|
1880
|
-
for (const [workType, pattern] of workPatterns) {
|
|
1881
|
-
if (pattern.test(lower)) return { kind: 'work-item', workType };
|
|
1882
|
-
}
|
|
1883
|
-
return null;
|
|
1926
|
+
if (!_messageHasDelegationIntent(message) && !_messageHasMediumLargeWorkIntent(message, { source })) return null;
|
|
1927
|
+
return { kind: 'work-item', workType: _inferDelegatedWorkType(message) };
|
|
1884
1928
|
}
|
|
1885
1929
|
|
|
1886
1930
|
function _ccInferMetadataActionIntent(meta) {
|
|
@@ -1892,52 +1936,64 @@ function _ccInferMetadataActionIntent(meta) {
|
|
|
1892
1936
|
}
|
|
1893
1937
|
|
|
1894
1938
|
function _ccActionIntentTargetText(message, intent) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
.
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
+
}
|
|
1903
1961
|
if (intent?.kind === 'plan') {
|
|
1904
|
-
|
|
1905
|
-
.replace(/^plan\s+(?:out\s+)?(?:for\s+|how\s+to\s+|this\s+|that\s+|it\s+)?/i, '');
|
|
1962
|
+
while (['make', 'create', 'draft', 'write', 'design', 'plan', 'out', 'a', 'an', 'the', 'for', 'to', 'about', 'how'].includes(tokens[start])) start++;
|
|
1906
1963
|
} else if (intent?.workType) {
|
|
1907
|
-
|
|
1908
|
-
const verbPattern = {
|
|
1909
|
-
fix: /^(?:fix|repair|resolve|patch|debug)\s+(?:the\s+|a\s+|an\s+)?/i,
|
|
1910
|
-
implement: /^(?:implement|build|add|create|ship)\s+(?:the\s+|a\s+|an\s+)?/i,
|
|
1911
|
-
review: /^(?:review|code review|inspect|audit)\s+(?:the\s+|a\s+|an\s+)?/i,
|
|
1912
|
-
test: /^(?:test|verify|validate|run tests?\s+(?:for\s+|on\s+)?|write tests?\s+(?:for\s+)?|add tests?\s+(?:for\s+)?)\s*(?:the\s+|a\s+|an\s+)?/i,
|
|
1913
|
-
explore: /^(?:explore|investigate|research|analyze|understand|look into|map out|survey)\s+(?:the\s+|a\s+|an\s+)?/i,
|
|
1914
|
-
}[workType];
|
|
1915
|
-
if (verbPattern) text = text.replace(verbPattern, '');
|
|
1916
|
-
text = text.replace(/^(?:a\s+)?(?:fix|review|test|implementation|exploration|investigation)\s+(?:for\s+|to\s+|of\s+)?/i, '');
|
|
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++;
|
|
1917
1965
|
}
|
|
1918
|
-
|
|
1966
|
+
while (['for', 'to', 'of', 'on', 'about', 'a', 'an', 'the'].includes(tokens[start])) start++;
|
|
1967
|
+
return tokens.slice(start).join(' ');
|
|
1919
1968
|
}
|
|
1920
1969
|
|
|
1921
1970
|
function _ccIntentHasConcreteTarget(message, metadataTitle, intent) {
|
|
1922
1971
|
const raw = String(message || '');
|
|
1923
|
-
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
.
|
|
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();
|
|
1928
1981
|
if (!target) return false;
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
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;
|
|
1932
1985
|
return target.length >= 3;
|
|
1933
1986
|
}
|
|
1934
1987
|
|
|
1935
1988
|
function _ccIntentTitle(message, metadataTitle, intent) {
|
|
1936
1989
|
const metaTitle = _ccCleanIntentString(metadataTitle, 300);
|
|
1937
1990
|
if (metaTitle && _ccIntentHasConcreteTarget(message, metaTitle, intent)) return metaTitle;
|
|
1938
|
-
const cleaned = _ccCleanIntentString(message, 300)
|
|
1991
|
+
const cleaned = _ccCleanIntentString(message, 300);
|
|
1939
1992
|
if (!cleaned) return '';
|
|
1940
|
-
|
|
1993
|
+
const title = intent?.kind === 'plan'
|
|
1994
|
+
? cleaned
|
|
1995
|
+
: _delegatedWorkTitle(cleaned, intent?.workType || 'ask');
|
|
1996
|
+
return title.charAt(0).toUpperCase() + title.slice(1);
|
|
1941
1997
|
}
|
|
1942
1998
|
|
|
1943
1999
|
function _ccFallbackMissingTargetError(intent) {
|
|
@@ -1945,17 +2001,20 @@ function _ccFallbackMissingTargetError(intent) {
|
|
|
1945
2001
|
return `Missing target for ${label} request. Specify a concrete title, PR/work item, file, or feature to ${label}.`;
|
|
1946
2002
|
}
|
|
1947
2003
|
|
|
1948
|
-
function _actionsWithIntentFallback(actions,
|
|
1949
|
-
const
|
|
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;
|
|
1950
2009
|
if (existing.length > 0) return existing;
|
|
1951
2010
|
const meta = _ccNormalizeIntentMetadata(intentMetadata);
|
|
1952
2011
|
const messageIntent = _ccInferMessageActionIntent(message, source);
|
|
1953
2012
|
const metadataIntent = source === 'doc-chat' ? null : _ccInferMetadataActionIntent(meta);
|
|
1954
2013
|
const intent = messageIntent || metadataIntent;
|
|
1955
|
-
if (!intent) return existing;
|
|
2014
|
+
if (!intent) return _ensureDelegationForIntent(existing, { message, source, filePath, title: docTitle, answerText, toolUses });
|
|
1956
2015
|
|
|
1957
2016
|
const title = _ccIntentTitle(message, meta.title, intent);
|
|
1958
|
-
const hasTarget = _ccIntentHasConcreteTarget(message, meta.title
|
|
2017
|
+
const hasTarget = _ccIntentHasConcreteTarget(message, meta.title, intent);
|
|
1959
2018
|
const description = meta.description || _ccCleanIntentString(message, 2000);
|
|
1960
2019
|
const common = {
|
|
1961
2020
|
...(title ? { title } : {}),
|
|
@@ -2038,6 +2097,261 @@ function parseCCActions(text) {
|
|
|
2038
2097
|
return result;
|
|
2039
2098
|
}
|
|
2040
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
|
+
|
|
2041
2355
|
function stripCCActionSyntax(text) {
|
|
2042
2356
|
if (!text) return '';
|
|
2043
2357
|
let displayText = text;
|
|
@@ -2414,9 +2728,10 @@ function createPipelineFromAction(action) {
|
|
|
2414
2728
|
// before `try` to avoid filling `results` with cryptic per-handler error messages.
|
|
2415
2729
|
function _ccValidateAction(action) {
|
|
2416
2730
|
if (!action || typeof action !== 'object' || !action.type) return 'action is missing required field: type';
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
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`;
|
|
2420
2735
|
return null;
|
|
2421
2736
|
case 'implement':
|
|
2422
2737
|
return 'Unsupported action type "implement"; use type="dispatch" with workType="implement".';
|
|
@@ -2760,7 +3075,7 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
|
|
|
2760
3075
|
}
|
|
2761
3076
|
try {
|
|
2762
3077
|
switch (action.type) {
|
|
2763
|
-
case 'dispatch':
|
|
3078
|
+
case 'dispatch': {
|
|
2764
3079
|
const workType = routing.normalizeWorkType(action.workType || (action.type !== 'dispatch' ? action.type : WORK_TYPE.IMPLEMENT), WORK_TYPE.IMPLEMENT);
|
|
2765
3080
|
const id = 'W-' + shared.uid();
|
|
2766
3081
|
const project = action.project || '';
|
|
@@ -2827,7 +3142,7 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
|
|
|
2827
3142
|
|
|
2828
3143
|
// Issue #1772: CC review/explore/test are human-initiated one-offs.
|
|
2829
3144
|
// Mark oneShot so any discovered PR is tagged _contextOnly (skips eval loop).
|
|
2830
|
-
const ccOneShotTypes = new Set([
|
|
3145
|
+
const ccOneShotTypes = new Set([WORK_TYPE.REVIEW, WORK_TYPE.EXPLORE, WORK_TYPE.TEST, WORK_TYPE.ASK, WORK_TYPE.VERIFY]);
|
|
2831
3146
|
const isOneShot = action.oneShot === true || (action.oneShot !== false && ccOneShotTypes.has(workType));
|
|
2832
3147
|
const item = {
|
|
2833
3148
|
id, title: action.title.trim(), type: workType,
|
|
@@ -3054,7 +3369,7 @@ async function executeDocChatActions(actions, { filePath = null } = {}) {
|
|
|
3054
3369
|
return executeCCActions(actions, { source: 'doc-chat', inferredProject: _inferDocChatProject(filePath) });
|
|
3055
3370
|
}
|
|
3056
3371
|
|
|
3057
|
-
const DOC_CHAT_WORK_ITEM_ACTION_TYPES = new Set(['dispatch', 'fix', 'explore', 'review', 'test']);
|
|
3372
|
+
const DOC_CHAT_WORK_ITEM_ACTION_TYPES = new Set(['dispatch', 'fix', 'implement', 'implement:large', 'explore', 'review', 'test', 'ask', 'verify']);
|
|
3058
3373
|
|
|
3059
3374
|
function _buildDocChatActionFeedback(actions, actionResults) {
|
|
3060
3375
|
if (!Array.isArray(actions) || !Array.isArray(actionResults)) return [];
|
|
@@ -3855,8 +4170,9 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
3855
4170
|
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
3856
4171
|
// Match Command Center's full tool surface and turn budget so doc-chat
|
|
3857
4172
|
// can take action (read/write/edit/dispatch) instead of being limited
|
|
3858
|
-
// to Q&A. The doc-chat sysprompt
|
|
3859
|
-
//
|
|
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.
|
|
3860
4176
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
3861
4177
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
3862
4178
|
transcript,
|
|
@@ -4750,6 +5066,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
4750
5066
|
try {
|
|
4751
5067
|
const body = await readBody(req);
|
|
4752
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 });
|
|
4753
5071
|
// Write as a work item with type 'plan' — user must explicitly execute plan-to-prd after reviewing
|
|
4754
5072
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
4755
5073
|
const id = 'W-' + shared.uid();
|
|
@@ -4759,7 +5077,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4759
5077
|
status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
|
|
4760
5078
|
branchStrategy: body.branch_strategy || body.branchStrategy || 'parallel',
|
|
4761
5079
|
};
|
|
4762
|
-
if (
|
|
5080
|
+
if (target.project) item.project = target.project.name;
|
|
4763
5081
|
if (body.agent) item.agent = body.agent;
|
|
4764
5082
|
mutateWorkItems(wiPath, items => { items.push(item); });
|
|
4765
5083
|
return jsonReply(res, 200, { ok: true, id, agent: body.agent || '' });
|
|
@@ -4770,6 +5088,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
4770
5088
|
try {
|
|
4771
5089
|
const body = await readBody(req);
|
|
4772
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 });
|
|
4773
5093
|
|
|
4774
5094
|
if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
|
|
4775
5095
|
|
|
@@ -4777,7 +5097,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4777
5097
|
const planFile = 'manual-' + shared.uid() + '.json';
|
|
4778
5098
|
const plan = {
|
|
4779
5099
|
version: 'manual-' + new Date().toISOString().slice(0, 10),
|
|
4780
|
-
project:
|
|
5100
|
+
project: target.project?.name || (PROJECTS.length > 0 ? PROJECTS[0].name : 'Unknown'),
|
|
4781
5101
|
generated_by: 'dashboard',
|
|
4782
5102
|
generated_at: new Date().toISOString().slice(0, 10),
|
|
4783
5103
|
plan_summary: body.name,
|
|
@@ -6088,15 +6408,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6088
6408
|
transcript: body.transcript,
|
|
6089
6409
|
onAbortReady: (abort) => { _docAbort = abort; },
|
|
6090
6410
|
});
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
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);
|
|
6094
6416
|
const finalize = _finalizeDocChatEdit({
|
|
6095
6417
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
6096
6418
|
originalContent: currentContent, delimiterContent: content,
|
|
6097
6419
|
});
|
|
6098
6420
|
const payload = _buildDocChatResponsePayload({
|
|
6099
|
-
answer, actions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6421
|
+
answer, actions: delegatedActions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6100
6422
|
});
|
|
6101
6423
|
_docDone = true;
|
|
6102
6424
|
return jsonReply(res, 200, payload);
|
|
@@ -6184,15 +6506,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6184
6506
|
onToolUse: (name, input) => { writeDocEvent({ type: 'tool', name, input: _lightToolInput(input) }); },
|
|
6185
6507
|
onRetry: (attempt) => { writeDocEvent({ type: 'progress', attempt }); },
|
|
6186
6508
|
});
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
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);
|
|
6190
6514
|
const finalize = _finalizeDocChatEdit({
|
|
6191
6515
|
filePath: body.filePath, fullPath, isJson, canEdit,
|
|
6192
6516
|
originalContent: currentContent, delimiterContent: content,
|
|
6193
6517
|
});
|
|
6194
6518
|
const payload = _buildDocChatResponsePayload({
|
|
6195
|
-
answer, actions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6519
|
+
answer, actions: delegatedActions, actionResults, actionFeedback, actionParseError, ccError, partial, warning, toolUses, finalize,
|
|
6196
6520
|
});
|
|
6197
6521
|
const { answer: finalAnswer, ...donePayload } = payload;
|
|
6198
6522
|
writeDocEvent({
|
|
@@ -6713,13 +7037,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6713
7037
|
});
|
|
6714
7038
|
}
|
|
6715
7039
|
|
|
6716
|
-
const parsed = parseCCActions(result.text);
|
|
6717
|
-
parsed.actions = _actionsWithIntentFallback(parsed.actions, {
|
|
6718
|
-
message: body.message,
|
|
6719
|
-
intentMetadata: body.intentMetadata,
|
|
6720
|
-
source: 'command-center',
|
|
6721
|
-
});
|
|
6722
7040
|
const toolUses = Array.isArray(result.toolUses) ? result.toolUses : _extractToolUsesFromRaw(result.raw);
|
|
7041
|
+
const parsed = parseCCActions(result.text);
|
|
6723
7042
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
6724
7043
|
const _loopWatch = _detectLoopInvocation(parsed.text, parsed.actions, toolUses, body.message);
|
|
6725
7044
|
if (_loopWatch) {
|
|
@@ -6727,7 +7046,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6727
7046
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
6728
7047
|
try { shared.log('warn', '/loop invocation detected in CC response — auto-converted to create-watch'); } catch {}
|
|
6729
7048
|
}
|
|
6730
|
-
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
|
+
);
|
|
6731
7053
|
if (parsed.actions.length > 0) {
|
|
6732
7054
|
parsed.actionResults = await executeCCActions(parsed.actions);
|
|
6733
7055
|
}
|
|
@@ -7048,11 +7370,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7048
7370
|
|
|
7049
7371
|
// Send final result with actions — execute server-side first
|
|
7050
7372
|
let { text: displayText, actions, _actionParseError } = parseCCActions(result.text);
|
|
7051
|
-
actions = _actionsWithIntentFallback(actions, {
|
|
7052
|
-
message: body.message,
|
|
7053
|
-
intentMetadata: body.intentMetadata,
|
|
7054
|
-
source: 'command-center',
|
|
7055
|
-
});
|
|
7056
7373
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
7057
7374
|
const _loopWatch = _detectLoopInvocation(displayText, actions, toolUses, body.message);
|
|
7058
7375
|
if (_loopWatch) {
|
|
@@ -7060,7 +7377,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7060
7377
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
7061
7378
|
try { shared.log('warn', '/loop invocation detected in CC response — auto-converted to create-watch'); } catch {}
|
|
7062
7379
|
}
|
|
7063
|
-
actions =
|
|
7380
|
+
actions = _actionsWithIntentFallback(
|
|
7381
|
+
_filterImplicitPostDispatchActions(actions, body.message),
|
|
7382
|
+
{ message: body.message, intentMetadata: body.intentMetadata, source: 'command-center', answerText: displayText, toolUses }
|
|
7383
|
+
);
|
|
7064
7384
|
let actionResults;
|
|
7065
7385
|
if (actions.length > 0) {
|
|
7066
7386
|
actionResults = await executeCCActions(actions);
|
|
@@ -7124,6 +7444,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7124
7444
|
const body = await readBody(req);
|
|
7125
7445
|
let { id, cron, title, type, project, agent, description, priority, enabled } = body;
|
|
7126
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;
|
|
7127
7450
|
|
|
7128
7451
|
// Auto-generate ID from title if not provided
|
|
7129
7452
|
if (!id) {
|
|
@@ -7156,8 +7479,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7156
7479
|
|
|
7157
7480
|
async function handleSchedulesUpdate(req, res) {
|
|
7158
7481
|
const body = await readBody(req);
|
|
7159
|
-
|
|
7482
|
+
let { id, cron, title, type, project, agent, description, priority, enabled } = body;
|
|
7160
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
|
+
}
|
|
7161
7489
|
|
|
7162
7490
|
let missingSchedules = false;
|
|
7163
7491
|
let sched = null;
|
|
@@ -7218,6 +7546,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7218
7546
|
reloadConfig();
|
|
7219
7547
|
const sched = (CONFIG.schedules || []).find(s => s.id === id);
|
|
7220
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
|
+
}
|
|
7221
7554
|
|
|
7222
7555
|
const schedulerMod = require('./engine/scheduler');
|
|
7223
7556
|
let item;
|
|
@@ -8325,8 +8658,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8325
8658
|
const { savePipeline, getPipeline } = require('./engine/pipeline');
|
|
8326
8659
|
if (getPipeline(body.id)) return jsonReply(res, 409, { error: 'Pipeline already exists' });
|
|
8327
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;
|
|
8328
8663
|
if (body.stopWhen) pipeline.stopWhen = body.stopWhen;
|
|
8329
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 });
|
|
8330
8667
|
savePipeline(pipeline);
|
|
8331
8668
|
invalidateStatusCache();
|
|
8332
8669
|
return jsonReply(res, 200, { ok: true, id: pipeline.id });
|
|
@@ -8341,8 +8678,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8341
8678
|
if (body.stages !== undefined) pipeline.stages = body.stages;
|
|
8342
8679
|
if (body.trigger !== undefined) pipeline.trigger = body.trigger;
|
|
8343
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;
|
|
8344
8683
|
if (body.monitoredResources !== undefined) pipeline.monitoredResources = body.monitoredResources;
|
|
8345
8684
|
if (body.stopWhen !== undefined) pipeline.stopWhen = body.stopWhen;
|
|
8685
|
+
const projectError = validatePipelineProjects(pipeline);
|
|
8686
|
+
if (projectError) return jsonReply(res, 400, { error: projectError });
|
|
8346
8687
|
savePipeline(pipeline);
|
|
8347
8688
|
invalidateStatusCache();
|
|
8348
8689
|
return jsonReply(res, 200, { ok: true });
|
|
@@ -8686,6 +9027,11 @@ module.exports = {
|
|
|
8686
9027
|
_shouldSuppressDocChatPostPatchError,
|
|
8687
9028
|
_buildDocChatResponsePayload,
|
|
8688
9029
|
_inferDocChatProject,
|
|
9030
|
+
_messageHasDelegationIntent,
|
|
9031
|
+
_messageRequestsDirectHandling,
|
|
9032
|
+
_messageHasMediumLargeWorkIntent,
|
|
9033
|
+
_inferDelegationActionFromMessage,
|
|
9034
|
+
_ensureDelegationForIntent,
|
|
8689
9035
|
_linkPullRequestForTracking: linkPullRequestForTracking,
|
|
8690
9036
|
_resolveSkillReadPath,
|
|
8691
9037
|
DOC_CHAT_DOCUMENT_DELIMITER,
|