helloagents 3.0.2-beta.1 → 3.0.7
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +147 -45
- package/README_CN.md +148 -46
- package/bootstrap-lite.md +104 -46
- package/bootstrap.md +143 -112
- package/cli.mjs +80 -427
- package/gemini-extension.json +1 -1
- package/hooks/hooks-claude.json +10 -0
- package/hooks/hooks.json +10 -0
- package/package.json +2 -12
- package/scripts/advisor-state.mjs +222 -0
- package/scripts/capability-registry.mjs +59 -0
- package/scripts/cli-codex-backup.mjs +59 -0
- package/scripts/cli-codex-config.mjs +100 -0
- package/scripts/cli-codex.mjs +34 -156
- package/scripts/cli-config.mjs +1 -0
- package/scripts/cli-doctor-render.mjs +28 -0
- package/scripts/cli-doctor.mjs +367 -0
- package/scripts/cli-host-detect.mjs +94 -0
- package/scripts/cli-lifecycle-hosts.mjs +123 -0
- package/scripts/cli-lifecycle.mjs +213 -0
- package/scripts/cli-messages.mjs +76 -52
- package/scripts/closeout-state.mjs +213 -0
- package/scripts/delivery-gate.mjs +256 -0
- package/scripts/guard-rules.mjs +122 -0
- package/scripts/guard.mjs +190 -168
- package/scripts/notify-context.mjs +77 -17
- package/scripts/notify-events.mjs +5 -1
- package/scripts/notify-route.mjs +111 -0
- package/scripts/notify-shared.mjs +0 -2
- package/scripts/notify-source.mjs +113 -0
- package/scripts/notify-ui.mjs +40 -6
- package/scripts/notify.mjs +120 -59
- package/scripts/plan-contract.mjs +210 -0
- package/scripts/project-storage.mjs +235 -0
- package/scripts/ralph-loop.mjs +9 -58
- package/scripts/replay-state.mjs +210 -0
- package/scripts/review-state.mjs +220 -0
- package/scripts/runtime-context.mjs +74 -0
- package/scripts/verify-state.mjs +226 -0
- package/scripts/visual-state.mjs +244 -0
- package/scripts/workflow-core.mjs +165 -0
- package/scripts/workflow-plan-files.mjs +249 -0
- package/scripts/workflow-recommendation.mjs +335 -0
- package/scripts/workflow-state.mjs +113 -0
- package/skills/commands/auto/SKILL.md +37 -71
- package/skills/commands/build/SKILL.md +67 -0
- package/skills/commands/clean/SKILL.md +10 -8
- package/skills/commands/commit/SKILL.md +8 -4
- package/skills/commands/help/SKILL.md +19 -11
- package/skills/commands/idea/SKILL.md +55 -0
- package/skills/commands/init/SKILL.md +6 -3
- package/skills/commands/loop/SKILL.md +6 -5
- package/skills/commands/plan/SKILL.md +116 -0
- package/skills/commands/prd/SKILL.md +20 -15
- package/skills/commands/verify/SKILL.md +32 -9
- package/skills/commands/wiki/SKILL.md +59 -0
- package/skills/hello-review/SKILL.md +9 -0
- package/skills/hello-subagent/SKILL.md +4 -3
- package/skills/hello-ui/SKILL.md +36 -8
- package/skills/hello-verify/SKILL.md +10 -2
- package/skills/helloagents/SKILL.md +24 -13
- package/templates/DESIGN.md +25 -4
- package/templates/STATE.md +3 -0
- package/templates/plans/contract.json +48 -0
- package/templates/plans/plan.md +23 -0
- package/templates/plans/tasks.md +3 -3
- package/skills/commands/design/SKILL.md +0 -108
- package/skills/commands/review/SKILL.md +0 -16
- package/templates/plans/design.md +0 -14
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export function resolveBootstrapFile(cwd, installMode) {
|
|
5
|
+
const isActivated = existsSync(join(cwd, '.helloagents'))
|
|
6
|
+
return (installMode === 'global' || isActivated) ? 'bootstrap.md' : 'bootstrap-lite.md'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function shouldBypassRoute(prompt) {
|
|
10
|
+
return !prompt || /^\[子代理任务\]/.test(prompt)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildHelpExtraRules(skillName) {
|
|
14
|
+
if (skillName !== 'help') return ''
|
|
15
|
+
return ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前上下文中已注入的“当前用户设置”,只有上下文不存在该信息时才尝试读取 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或已激活项目中生效。不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示。'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function routeExplicitCommand({
|
|
19
|
+
prompt,
|
|
20
|
+
cwd,
|
|
21
|
+
host,
|
|
22
|
+
pkgRoot,
|
|
23
|
+
settings,
|
|
24
|
+
resolveCanonicalCommandSkill,
|
|
25
|
+
writeRouteContext,
|
|
26
|
+
appendReplayEvent,
|
|
27
|
+
buildRouteInstruction,
|
|
28
|
+
suppress,
|
|
29
|
+
}) {
|
|
30
|
+
const cmdMatch = prompt.match(/^~(\w+)/)
|
|
31
|
+
if (!cmdMatch) return false
|
|
32
|
+
|
|
33
|
+
const skillName = cmdMatch[1]
|
|
34
|
+
const canonicalSkillName = resolveCanonicalCommandSkill(skillName)
|
|
35
|
+
writeRouteContext({
|
|
36
|
+
cwd,
|
|
37
|
+
skillName: canonicalSkillName,
|
|
38
|
+
sourceSkillName: skillName,
|
|
39
|
+
})
|
|
40
|
+
appendReplayEvent(cwd, {
|
|
41
|
+
host,
|
|
42
|
+
event: 'command_route_selected',
|
|
43
|
+
source: 'route',
|
|
44
|
+
skillName: canonicalSkillName,
|
|
45
|
+
sourceSkillName: skillName,
|
|
46
|
+
})
|
|
47
|
+
suppress(buildRouteInstruction({
|
|
48
|
+
skillName,
|
|
49
|
+
extraRules: buildHelpExtraRules(skillName),
|
|
50
|
+
cwd,
|
|
51
|
+
pkgRoot,
|
|
52
|
+
host,
|
|
53
|
+
settings,
|
|
54
|
+
}))
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function handleRouteCommand({
|
|
59
|
+
payload,
|
|
60
|
+
host,
|
|
61
|
+
pkgRoot,
|
|
62
|
+
settings,
|
|
63
|
+
buildRouteInstruction,
|
|
64
|
+
buildSemanticRouteInstruction,
|
|
65
|
+
resolveCanonicalCommandSkill,
|
|
66
|
+
writeRouteContext,
|
|
67
|
+
clearRouteContext,
|
|
68
|
+
appendReplayEvent,
|
|
69
|
+
getWorkflowRecommendation,
|
|
70
|
+
suppress,
|
|
71
|
+
emptySuppress,
|
|
72
|
+
}) {
|
|
73
|
+
const prompt = (payload.prompt || '').trim()
|
|
74
|
+
const cwd = payload.cwd || process.cwd()
|
|
75
|
+
if (shouldBypassRoute(prompt)) {
|
|
76
|
+
clearRouteContext()
|
|
77
|
+
emptySuppress()
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (routeExplicitCommand({
|
|
82
|
+
prompt,
|
|
83
|
+
cwd,
|
|
84
|
+
host,
|
|
85
|
+
pkgRoot,
|
|
86
|
+
settings,
|
|
87
|
+
resolveCanonicalCommandSkill,
|
|
88
|
+
writeRouteContext,
|
|
89
|
+
appendReplayEvent,
|
|
90
|
+
buildRouteInstruction,
|
|
91
|
+
suppress,
|
|
92
|
+
})) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode)
|
|
97
|
+
if (bootstrapFile === 'bootstrap.md') {
|
|
98
|
+
clearRouteContext()
|
|
99
|
+
appendReplayEvent(cwd, {
|
|
100
|
+
host,
|
|
101
|
+
event: 'semantic_route_prompted',
|
|
102
|
+
source: 'route',
|
|
103
|
+
recommendation: getWorkflowRecommendation(cwd),
|
|
104
|
+
})
|
|
105
|
+
suppress(buildSemanticRouteInstruction(cwd))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
clearRouteContext()
|
|
110
|
+
emptySuppress()
|
|
111
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { basename, normalize, resolve } from 'node:path'
|
|
2
|
+
|
|
3
|
+
const HOST_LABELS = {
|
|
4
|
+
codex: 'Codex',
|
|
5
|
+
claude: 'Claude Code',
|
|
6
|
+
gemini: 'Gemini',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PAYLOAD_SESSION_KEYS = [
|
|
10
|
+
'sessionId',
|
|
11
|
+
'session_id',
|
|
12
|
+
'session',
|
|
13
|
+
'conversationId',
|
|
14
|
+
'conversation_id',
|
|
15
|
+
'conversation',
|
|
16
|
+
'threadId',
|
|
17
|
+
'thread_id',
|
|
18
|
+
'thread',
|
|
19
|
+
'windowId',
|
|
20
|
+
'window_id',
|
|
21
|
+
'window',
|
|
22
|
+
'tabId',
|
|
23
|
+
'tab_id',
|
|
24
|
+
'tab',
|
|
25
|
+
'requestId',
|
|
26
|
+
'request_id',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const ENV_SESSION_KEYS = [
|
|
30
|
+
'HELLOAGENTS_NOTIFY_SESSION_ID',
|
|
31
|
+
'WT_SESSION',
|
|
32
|
+
'TERM_SESSION_ID',
|
|
33
|
+
'KITTY_WINDOW_ID',
|
|
34
|
+
'ALACRITTY_WINDOW_ID',
|
|
35
|
+
'WINDOWID',
|
|
36
|
+
'WEZTERM_PANE',
|
|
37
|
+
'TAB_ID',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
function normalizePath(filePath = '') {
|
|
41
|
+
if (!filePath) return ''
|
|
42
|
+
try {
|
|
43
|
+
return normalize(resolve(filePath))
|
|
44
|
+
} catch {
|
|
45
|
+
return filePath
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readStringCandidate(input, key) {
|
|
50
|
+
if (!input || typeof input !== 'object') return ''
|
|
51
|
+
const value = input[key]
|
|
52
|
+
if (typeof value === 'string') return value.trim()
|
|
53
|
+
if (typeof value === 'number') return String(value)
|
|
54
|
+
return ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveProjectLabel(cwd = '') {
|
|
58
|
+
const normalized = normalizePath(cwd)
|
|
59
|
+
if (!normalized) return ''
|
|
60
|
+
const label = basename(normalized)
|
|
61
|
+
return label || normalized.replace(/\\/g, '/')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sanitizeSessionToken(value = '') {
|
|
65
|
+
const raw = String(value).trim().replace(/^[#:\s]+/, '')
|
|
66
|
+
const segments = raw
|
|
67
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
const cleaned = segments.length > 1
|
|
70
|
+
? segments[segments.length - 1]
|
|
71
|
+
: raw.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
72
|
+
|
|
73
|
+
if (!cleaned) return ''
|
|
74
|
+
if (/^\d+$/.test(cleaned)) return cleaned
|
|
75
|
+
return cleaned.slice(0, 8)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveSessionToken(payload, env, ppid) {
|
|
79
|
+
for (const key of PAYLOAD_SESSION_KEYS) {
|
|
80
|
+
const value = sanitizeSessionToken(readStringCandidate(payload, key))
|
|
81
|
+
if (value) return value
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const key of ENV_SESSION_KEYS) {
|
|
85
|
+
const value = sanitizeSessionToken(env?.[key] || '')
|
|
86
|
+
if (value) return value
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return ppid ? String(ppid) : ''
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveNotificationSource({
|
|
93
|
+
host = '',
|
|
94
|
+
cwd = '',
|
|
95
|
+
payload = {},
|
|
96
|
+
env = process.env,
|
|
97
|
+
ppid = process.ppid,
|
|
98
|
+
} = {}) {
|
|
99
|
+
const hostLabel = HOST_LABELS[host] || 'Agent'
|
|
100
|
+
const projectLabel = resolveProjectLabel(readStringCandidate(payload, 'cwd') || cwd)
|
|
101
|
+
const sessionToken = resolveSessionToken(payload, env, ppid)
|
|
102
|
+
const parts = [hostLabel]
|
|
103
|
+
|
|
104
|
+
if (projectLabel) parts.push(projectLabel)
|
|
105
|
+
if (sessionToken) parts.push(`会话 ${sessionToken}`)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
hostLabel,
|
|
109
|
+
projectLabel,
|
|
110
|
+
sessionToken,
|
|
111
|
+
sourceLabel: parts.join(' · '),
|
|
112
|
+
}
|
|
113
|
+
}
|
package/scripts/notify-ui.mjs
CHANGED
|
@@ -19,6 +19,36 @@ const NOTIFY_MESSAGES = {
|
|
|
19
19
|
|
|
20
20
|
const WIN_APPID = 'HelloAgents.Notification';
|
|
21
21
|
|
|
22
|
+
function escapeToastText(value = '') {
|
|
23
|
+
return String(value)
|
|
24
|
+
.replace(/&/g, '&')
|
|
25
|
+
.replace(/</g, '<')
|
|
26
|
+
.replace(/>/g, '>');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function escapeAppleScriptText(value = '') {
|
|
30
|
+
return String(value).replace(/"/g, '\\"');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildDesktopNotificationContent(event, extra) {
|
|
34
|
+
const options = extra && typeof extra === 'object'
|
|
35
|
+
? extra
|
|
36
|
+
: { message: extra || '' };
|
|
37
|
+
const message = options.message || NOTIFY_MESSAGES[event] || event;
|
|
38
|
+
const title = options.title || 'HelloAgents 通知';
|
|
39
|
+
const sourceLabel = options.sourceLabel || '';
|
|
40
|
+
const body = sourceLabel ? `${sourceLabel}\n${message}` : message;
|
|
41
|
+
const toastLines = sourceLabel ? [sourceLabel, message] : [message];
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
title,
|
|
45
|
+
message,
|
|
46
|
+
sourceLabel,
|
|
47
|
+
body,
|
|
48
|
+
toastLines,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
function resolveWav(pkgRoot, event) {
|
|
23
53
|
const p = join(pkgRoot, 'assets', 'sounds', `${event}.wav`);
|
|
24
54
|
return existsSync(p) ? p : null;
|
|
@@ -53,14 +83,15 @@ function ensureWinAppId(pkgRoot) {
|
|
|
53
83
|
}
|
|
54
84
|
|
|
55
85
|
export function desktopNotify(pkgRoot, event, extra) {
|
|
56
|
-
const
|
|
57
|
-
const title = 'HelloAgents 通知';
|
|
86
|
+
const notification = buildDesktopNotificationContent(event, extra);
|
|
58
87
|
try {
|
|
59
88
|
if (PLAT === 'win32') {
|
|
60
89
|
ensureWinAppId(pkgRoot);
|
|
61
|
-
const safeMsg = msg.replace(/'/g, "''");
|
|
62
90
|
const iconPath = join(pkgRoot, 'assets', 'icons', 'icon.png').replace(/\//g, '\\');
|
|
63
91
|
const iconXml = existsSync(iconPath) ? `<image placement="appLogoOverride" src="${iconPath}" />` : '';
|
|
92
|
+
const textXml = notification.toastLines
|
|
93
|
+
.map((line) => `<text>${escapeToastText(line)}</text>`)
|
|
94
|
+
.join('\n ');
|
|
64
95
|
const ps = `
|
|
65
96
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
66
97
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null
|
|
@@ -69,7 +100,7 @@ $xml = @"
|
|
|
69
100
|
<visual>
|
|
70
101
|
<binding template="ToastGeneric">
|
|
71
102
|
${iconXml}
|
|
72
|
-
|
|
103
|
+
${textXml}
|
|
73
104
|
</binding>
|
|
74
105
|
</visual>
|
|
75
106
|
</toast>
|
|
@@ -81,11 +112,14 @@ $toast = [Windows.UI.Notifications.ToastNotification]::new($doc)
|
|
|
81
112
|
`.trim();
|
|
82
113
|
spawnSync('powershell', ['-NoProfile', '-c', ps], { stdio: 'ignore', windowsHide: true });
|
|
83
114
|
} else if (PLAT === 'darwin') {
|
|
115
|
+
const subtitle = notification.sourceLabel
|
|
116
|
+
? ` subtitle "${escapeAppleScriptText(notification.sourceLabel)}"`
|
|
117
|
+
: '';
|
|
84
118
|
spawnSync('osascript', ['-e',
|
|
85
|
-
`display notification "${
|
|
119
|
+
`display notification "${escapeAppleScriptText(notification.message)}" with title "${escapeAppleScriptText(notification.title)}"${subtitle}`],
|
|
86
120
|
{ stdio: 'ignore' });
|
|
87
121
|
} else {
|
|
88
|
-
const result = spawnSync('notify-send', [title,
|
|
122
|
+
const result = spawnSync('notify-send', [notification.title, notification.body], { stdio: 'ignore' });
|
|
89
123
|
if (result.status !== 0) process.stderr.write('\x07');
|
|
90
124
|
}
|
|
91
125
|
} catch { process.stderr.write('\x07'); }
|
package/scripts/notify.mjs
CHANGED
|
@@ -8,9 +8,14 @@ import { spawnSync } from 'node:child_process';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { playSound as _playSound, desktopNotify as _desktopNotify } from './notify-ui.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveNotificationSource } from './notify-source.mjs';
|
|
12
|
+
import { buildCompactionContext, buildInjectContext, buildRouteInstruction, buildSemanticRouteInstruction, resolveCanonicalCommandSkill } from './notify-context.mjs';
|
|
12
13
|
import { claimsTaskComplete, shouldIgnoreCodexNotifyClient, shouldIgnoreFormattedSubagent } from './notify-events.mjs';
|
|
13
|
-
import {
|
|
14
|
+
import { handleRouteCommand, resolveBootstrapFile } from './notify-route.mjs';
|
|
15
|
+
import { readSettings, readStdinJson, output, suppressedOutput, emptySuppress } from './notify-shared.mjs';
|
|
16
|
+
import { clearRouteContext, writeRouteContext } from './runtime-context.mjs';
|
|
17
|
+
import { appendReplayEvent, startReplaySession } from './replay-state.mjs';
|
|
18
|
+
import { getWorkflowRecommendation } from './workflow-state.mjs';
|
|
14
19
|
|
|
15
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
21
|
const __dirname = dirname(__filename);
|
|
@@ -31,6 +36,18 @@ const EVENT_NAME = {
|
|
|
31
36
|
const playSound = (event) => _playSound(PKG_ROOT, event);
|
|
32
37
|
const desktopNotify = (event, extra) => _desktopNotify(PKG_ROOT, event, extra);
|
|
33
38
|
|
|
39
|
+
function buildNotifyExtra(payload = {}, options = {}) {
|
|
40
|
+
const source = resolveNotificationSource({
|
|
41
|
+
host: HOST,
|
|
42
|
+
cwd: payload.cwd || process.cwd(),
|
|
43
|
+
payload,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
message: options.message || '',
|
|
47
|
+
sourceLabel: source.sourceLabel,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
function getSettings() {
|
|
35
52
|
return readSettings(CONFIG_FILE);
|
|
36
53
|
}
|
|
@@ -41,7 +58,8 @@ function runRalphLoop(payload) {
|
|
|
41
58
|
try {
|
|
42
59
|
const rlPath = join(__dirname, 'ralph-loop.mjs');
|
|
43
60
|
if (!existsSync(rlPath)) return false;
|
|
44
|
-
const
|
|
61
|
+
const hostFlag = IS_GEMINI ? ['--gemini'] : HOST === 'codex' ? ['--codex'] : [];
|
|
62
|
+
const result = spawnSync(process.execPath, [rlPath, ...hostFlag], {
|
|
45
63
|
input: JSON.stringify(payload),
|
|
46
64
|
encoding: 'utf-8',
|
|
47
65
|
timeout: 120_000,
|
|
@@ -49,6 +67,12 @@ function runRalphLoop(payload) {
|
|
|
49
67
|
if (result.stdout) {
|
|
50
68
|
const rlOut = JSON.parse(result.stdout);
|
|
51
69
|
if (rlOut.decision === 'block') {
|
|
70
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
71
|
+
host: HOST,
|
|
72
|
+
event: 'verify_gate_blocked',
|
|
73
|
+
source: 'ralph-loop',
|
|
74
|
+
reason: rlOut.reason || '',
|
|
75
|
+
});
|
|
52
76
|
output(rlOut);
|
|
53
77
|
return true;
|
|
54
78
|
}
|
|
@@ -57,17 +81,44 @@ function runRalphLoop(payload) {
|
|
|
57
81
|
return false;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
function runDeliveryGate(payload) {
|
|
85
|
+
try {
|
|
86
|
+
const gatePath = join(__dirname, 'delivery-gate.mjs');
|
|
87
|
+
if (!existsSync(gatePath)) return false;
|
|
88
|
+
const result = spawnSync(process.execPath, [gatePath], {
|
|
89
|
+
input: JSON.stringify(payload),
|
|
90
|
+
encoding: 'utf-8',
|
|
91
|
+
timeout: 30_000,
|
|
92
|
+
});
|
|
93
|
+
if (result.stdout) {
|
|
94
|
+
const gateOut = JSON.parse(result.stdout);
|
|
95
|
+
if (gateOut.decision === 'block') {
|
|
96
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
97
|
+
host: HOST,
|
|
98
|
+
event: 'delivery_gate_blocked',
|
|
99
|
+
source: 'delivery-gate',
|
|
100
|
+
reason: gateOut.reason || '',
|
|
101
|
+
});
|
|
102
|
+
output(gateOut);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readCompletionText(payload = {}) {
|
|
111
|
+
return payload['last-assistant-message']
|
|
112
|
+
|| payload.last_assistant_message
|
|
113
|
+
|| payload.lastAssistantMessage
|
|
114
|
+
|| '';
|
|
64
115
|
}
|
|
65
116
|
|
|
66
117
|
function cmdPreCompact() {
|
|
67
118
|
const payload = readStdinJson();
|
|
68
119
|
const cwd = payload.cwd || process.cwd();
|
|
69
120
|
const settings = getSettings();
|
|
70
|
-
const bootstrapFile = resolveBootstrapFile(cwd, settings);
|
|
121
|
+
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode);
|
|
71
122
|
const context = buildCompactionContext({
|
|
72
123
|
payload,
|
|
73
124
|
pkgRoot: PKG_ROOT,
|
|
@@ -75,50 +126,35 @@ function cmdPreCompact() {
|
|
|
75
126
|
bootstrapFile,
|
|
76
127
|
host: HOST,
|
|
77
128
|
});
|
|
129
|
+
appendReplayEvent(cwd, {
|
|
130
|
+
host: HOST,
|
|
131
|
+
event: 'pre_compact_snapshot',
|
|
132
|
+
source: 'pre-compact',
|
|
133
|
+
details: {
|
|
134
|
+
bootstrapFile,
|
|
135
|
+
installMode: settings.install_mode || '',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
78
138
|
suppressedOutput(EVENT_NAME.PreCompact, context);
|
|
79
139
|
}
|
|
80
140
|
|
|
81
141
|
function cmdRoute() {
|
|
82
142
|
const payload = readStdinJson();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const extraRules = skillName === 'help'
|
|
99
|
-
? ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。适用条件:仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前上下文中已注入的“当前用户设置”,只有上下文不存在该信息时才尝试读取 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或当前项目已通过 ~init 激活后生效。排除条件:不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示;纯标准模式未激活项目不会自动触发。'
|
|
100
|
-
: '';
|
|
101
|
-
suppressedOutput(EVENT_NAME.UserPromptSubmit, buildRouteInstruction({
|
|
102
|
-
skillName,
|
|
103
|
-
extraRules,
|
|
104
|
-
cwd,
|
|
105
|
-
pkgRoot: PKG_ROOT,
|
|
106
|
-
host: HOST,
|
|
107
|
-
settings,
|
|
108
|
-
}));
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const bootstrapFile = resolveBootstrapFile(cwd, settings);
|
|
113
|
-
if (bootstrapFile === 'bootstrap.md') {
|
|
114
|
-
const routeMessage = detectNewProjectRoute(prompt);
|
|
115
|
-
if (routeMessage) {
|
|
116
|
-
suppressedOutput(EVENT_NAME.UserPromptSubmit, routeMessage);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
emptySuppress();
|
|
143
|
+
handleRouteCommand({
|
|
144
|
+
payload,
|
|
145
|
+
host: HOST,
|
|
146
|
+
pkgRoot: PKG_ROOT,
|
|
147
|
+
settings: getSettings(),
|
|
148
|
+
buildRouteInstruction,
|
|
149
|
+
buildSemanticRouteInstruction,
|
|
150
|
+
resolveCanonicalCommandSkill,
|
|
151
|
+
writeRouteContext,
|
|
152
|
+
clearRouteContext,
|
|
153
|
+
appendReplayEvent,
|
|
154
|
+
getWorkflowRecommendation,
|
|
155
|
+
suppress: (context) => suppressedOutput(EVENT_NAME.UserPromptSubmit, context),
|
|
156
|
+
emptySuppress,
|
|
157
|
+
});
|
|
122
158
|
}
|
|
123
159
|
|
|
124
160
|
function cmdInject() {
|
|
@@ -126,13 +162,29 @@ function cmdInject() {
|
|
|
126
162
|
const source = payload.source || 'startup';
|
|
127
163
|
const cwd = payload.cwd || process.cwd();
|
|
128
164
|
const settings = getSettings();
|
|
129
|
-
const bootstrapFile = resolveBootstrapFile(cwd, settings);
|
|
165
|
+
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode);
|
|
130
166
|
|
|
131
167
|
let bootstrap = '';
|
|
132
168
|
try {
|
|
133
169
|
bootstrap = readFileSync(join(PKG_ROOT, bootstrapFile), 'utf-8');
|
|
134
170
|
} catch {}
|
|
135
171
|
|
|
172
|
+
startReplaySession(cwd, {
|
|
173
|
+
host: HOST,
|
|
174
|
+
source,
|
|
175
|
+
bootstrapFile,
|
|
176
|
+
installMode: settings.install_mode || '',
|
|
177
|
+
});
|
|
178
|
+
appendReplayEvent(cwd, {
|
|
179
|
+
host: HOST,
|
|
180
|
+
event: 'session_injected',
|
|
181
|
+
source,
|
|
182
|
+
details: {
|
|
183
|
+
bootstrapFile,
|
|
184
|
+
installMode: settings.install_mode || '',
|
|
185
|
+
activatedProject: existsSync(join(cwd, '.helloagents')),
|
|
186
|
+
},
|
|
187
|
+
});
|
|
136
188
|
const context = buildInjectContext({
|
|
137
189
|
source,
|
|
138
190
|
bootstrap,
|
|
@@ -141,22 +193,30 @@ function cmdInject() {
|
|
|
141
193
|
host: HOST,
|
|
142
194
|
cwd,
|
|
143
195
|
});
|
|
196
|
+
clearRouteContext();
|
|
144
197
|
suppressedOutput(EVENT_NAME.SessionStart, context || undefined);
|
|
145
|
-
versionCheckBackground();
|
|
146
198
|
}
|
|
147
199
|
|
|
148
200
|
function cmdStop() {
|
|
149
201
|
const payload = readStdinJson();
|
|
202
|
+
const lastMsg = readCompletionText(payload);
|
|
203
|
+
const cwd = payload.cwd || process.cwd();
|
|
204
|
+
clearRouteContext();
|
|
150
205
|
if (runRalphLoop(payload)) {
|
|
151
206
|
playSound('warning');
|
|
152
|
-
desktopNotify('warning');
|
|
207
|
+
desktopNotify('warning', buildNotifyExtra(payload));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (claimsTaskComplete(lastMsg) && runDeliveryGate(payload)) {
|
|
211
|
+
playSound('warning');
|
|
212
|
+
desktopNotify('warning', buildNotifyExtra(payload));
|
|
153
213
|
return;
|
|
154
214
|
}
|
|
155
215
|
|
|
156
216
|
const settings = getSettings();
|
|
157
217
|
const level = settings.notify_level ?? 0;
|
|
158
218
|
if (level === 2 || level === 3) playSound('complete');
|
|
159
|
-
if (level === 1 || level === 3) desktopNotify('complete');
|
|
219
|
+
if (level === 1 || level === 3) desktopNotify('complete', buildNotifyExtra(payload));
|
|
160
220
|
emptySuppress();
|
|
161
221
|
}
|
|
162
222
|
|
|
@@ -165,7 +225,7 @@ function cmdSound() {
|
|
|
165
225
|
}
|
|
166
226
|
|
|
167
227
|
function cmdDesktop() {
|
|
168
|
-
desktopNotify(process.argv[3] || 'complete');
|
|
228
|
+
desktopNotify(process.argv[3] || 'complete', buildNotifyExtra({ cwd: process.cwd() }));
|
|
169
229
|
}
|
|
170
230
|
|
|
171
231
|
function cmdCodexNotify() {
|
|
@@ -178,7 +238,7 @@ function cmdCodexNotify() {
|
|
|
178
238
|
|
|
179
239
|
if (type === 'approval-requested') {
|
|
180
240
|
playSound('confirm');
|
|
181
|
-
desktopNotify('confirm');
|
|
241
|
+
desktopNotify('confirm', buildNotifyExtra(data));
|
|
182
242
|
return;
|
|
183
243
|
}
|
|
184
244
|
if (type !== 'agent-turn-complete') return;
|
|
@@ -190,18 +250,20 @@ function cmdCodexNotify() {
|
|
|
190
250
|
const cwd = data.cwd || process.cwd();
|
|
191
251
|
if (claimsTaskComplete(lastMsg) && runRalphLoop({ cwd })) {
|
|
192
252
|
playSound('warning');
|
|
193
|
-
desktopNotify('warning');
|
|
253
|
+
desktopNotify('warning', buildNotifyExtra(data));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (claimsTaskComplete(lastMsg) && runDeliveryGate({ cwd })) {
|
|
257
|
+
playSound('warning');
|
|
258
|
+
desktopNotify('warning', buildNotifyExtra(data));
|
|
194
259
|
return;
|
|
195
260
|
}
|
|
196
261
|
|
|
197
262
|
const level = settings.notify_level ?? 0;
|
|
198
263
|
if (level === 2 || level === 3) playSound('complete');
|
|
199
|
-
if (level === 1 || level === 3) desktopNotify('complete');
|
|
200
|
-
versionCheckBackground();
|
|
264
|
+
if (level === 1 || level === 3) desktopNotify('complete', buildNotifyExtra(data));
|
|
201
265
|
}
|
|
202
266
|
|
|
203
|
-
function cmdVersionCheck() {}
|
|
204
|
-
|
|
205
267
|
const cmd = process.argv[2] || '';
|
|
206
268
|
|
|
207
269
|
switch (cmd) {
|
|
@@ -212,7 +274,6 @@ switch (cmd) {
|
|
|
212
274
|
case 'sound': cmdSound(); break;
|
|
213
275
|
case 'desktop': cmdDesktop(); break;
|
|
214
276
|
case 'codex-notify': cmdCodexNotify(); break;
|
|
215
|
-
case 'version-check': cmdVersionCheck(); break;
|
|
216
277
|
default:
|
|
217
278
|
process.stderr.write(`notify.mjs: unknown command "${cmd}"\n`);
|
|
218
279
|
process.exit(1);
|