helloagents 3.0.3-beta.1 → 3.0.8-beta.1
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 +157 -57
- package/README_CN.md +157 -57
- package/bootstrap-lite.md +125 -50
- package/bootstrap.md +169 -123
- 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 +1 -1
- 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 +94 -0
- package/scripts/cli-codex.mjs +90 -222
- package/scripts/cli-config.mjs +1 -0
- package/scripts/cli-doctor-render.mjs +28 -0
- package/scripts/cli-doctor.mjs +370 -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/cli-toml.mjs +30 -0
- package/scripts/closeout-state.mjs +213 -0
- package/scripts/delivery-gate.mjs +256 -0
- package/scripts/guard-rules.mjs +147 -0
- package/scripts/guard.mjs +218 -168
- package/scripts/notify-context.mjs +78 -23
- 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 +137 -65
- 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/turn-state.mjs +173 -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/_meta/SKILL.md +1 -1
- package/skills/commands/auto/SKILL.md +48 -67
- 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 +18 -11
- package/skills/commands/idea/SKILL.md +55 -0
- package/skills/commands/init/SKILL.md +16 -8
- package/skills/commands/loop/SKILL.md +6 -5
- package/skills/commands/plan/SKILL.md +118 -0
- package/skills/commands/prd/SKILL.md +22 -15
- package/skills/commands/verify/SKILL.md +32 -9
- package/skills/commands/wiki/SKILL.md +11 -11
- package/skills/hello-review/SKILL.md +9 -0
- package/skills/hello-subagent/SKILL.md +5 -3
- package/skills/hello-ui/SKILL.md +36 -8
- package/skills/hello-verify/SKILL.md +12 -3
- package/skills/helloagents/SKILL.md +36 -20
- 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,15 @@ 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 {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { resolveNotificationSource } from './notify-source.mjs';
|
|
12
|
+
import { buildCompactionContext, buildInjectContext, buildRouteInstruction, buildSemanticRouteInstruction, resolveCanonicalCommandSkill } from './notify-context.mjs';
|
|
13
|
+
import { claimsTaskComplete, shouldIgnoreCodexNotifyClient } from './notify-events.mjs';
|
|
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 { clearTurnState, readTurnState } from './turn-state.mjs';
|
|
19
|
+
import { getWorkflowRecommendation } from './workflow-state.mjs';
|
|
14
20
|
|
|
15
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
22
|
const __dirname = dirname(__filename);
|
|
@@ -31,6 +37,18 @@ const EVENT_NAME = {
|
|
|
31
37
|
const playSound = (event) => _playSound(PKG_ROOT, event);
|
|
32
38
|
const desktopNotify = (event, extra) => _desktopNotify(PKG_ROOT, event, extra);
|
|
33
39
|
|
|
40
|
+
function buildNotifyExtra(payload = {}, options = {}) {
|
|
41
|
+
const source = resolveNotificationSource({
|
|
42
|
+
host: HOST,
|
|
43
|
+
cwd: payload.cwd || process.cwd(),
|
|
44
|
+
payload,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
message: options.message || '',
|
|
48
|
+
sourceLabel: source.sourceLabel,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
function getSettings() {
|
|
35
53
|
return readSettings(CONFIG_FILE);
|
|
36
54
|
}
|
|
@@ -41,7 +59,8 @@ function runRalphLoop(payload) {
|
|
|
41
59
|
try {
|
|
42
60
|
const rlPath = join(__dirname, 'ralph-loop.mjs');
|
|
43
61
|
if (!existsSync(rlPath)) return false;
|
|
44
|
-
const
|
|
62
|
+
const hostFlag = IS_GEMINI ? ['--gemini'] : HOST === 'codex' ? ['--codex'] : [];
|
|
63
|
+
const result = spawnSync(process.execPath, [rlPath, ...hostFlag], {
|
|
45
64
|
input: JSON.stringify(payload),
|
|
46
65
|
encoding: 'utf-8',
|
|
47
66
|
timeout: 120_000,
|
|
@@ -49,6 +68,12 @@ function runRalphLoop(payload) {
|
|
|
49
68
|
if (result.stdout) {
|
|
50
69
|
const rlOut = JSON.parse(result.stdout);
|
|
51
70
|
if (rlOut.decision === 'block') {
|
|
71
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
72
|
+
host: HOST,
|
|
73
|
+
event: 'verify_gate_blocked',
|
|
74
|
+
source: 'ralph-loop',
|
|
75
|
+
reason: rlOut.reason || '',
|
|
76
|
+
});
|
|
52
77
|
output(rlOut);
|
|
53
78
|
return true;
|
|
54
79
|
}
|
|
@@ -57,17 +82,52 @@ function runRalphLoop(payload) {
|
|
|
57
82
|
return false;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
function runDeliveryGate(payload) {
|
|
86
|
+
try {
|
|
87
|
+
const gatePath = join(__dirname, 'delivery-gate.mjs');
|
|
88
|
+
if (!existsSync(gatePath)) return false;
|
|
89
|
+
const result = spawnSync(process.execPath, [gatePath], {
|
|
90
|
+
input: JSON.stringify(payload),
|
|
91
|
+
encoding: 'utf-8',
|
|
92
|
+
timeout: 30_000,
|
|
93
|
+
});
|
|
94
|
+
if (result.stdout) {
|
|
95
|
+
const gateOut = JSON.parse(result.stdout);
|
|
96
|
+
if (gateOut.decision === 'block') {
|
|
97
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
98
|
+
host: HOST,
|
|
99
|
+
event: 'delivery_gate_blocked',
|
|
100
|
+
source: 'delivery-gate',
|
|
101
|
+
reason: gateOut.reason || '',
|
|
102
|
+
});
|
|
103
|
+
output(gateOut);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readCompletionText(payload = {}) {
|
|
112
|
+
return payload['last-assistant-message']
|
|
113
|
+
|| payload.last_assistant_message
|
|
114
|
+
|| payload.lastAssistantMessage
|
|
115
|
+
|| '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function shouldRunDeliveryGate(cwd, lastMsg) {
|
|
119
|
+
const turnState = readTurnState(cwd);
|
|
120
|
+
if (turnState?.role === 'main') {
|
|
121
|
+
return turnState.kind === 'complete';
|
|
122
|
+
}
|
|
123
|
+
return claimsTaskComplete(lastMsg);
|
|
64
124
|
}
|
|
65
125
|
|
|
66
126
|
function cmdPreCompact() {
|
|
67
127
|
const payload = readStdinJson();
|
|
68
128
|
const cwd = payload.cwd || process.cwd();
|
|
69
129
|
const settings = getSettings();
|
|
70
|
-
const bootstrapFile = resolveBootstrapFile(cwd, settings);
|
|
130
|
+
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode);
|
|
71
131
|
const context = buildCompactionContext({
|
|
72
132
|
payload,
|
|
73
133
|
pkgRoot: PKG_ROOT,
|
|
@@ -75,50 +135,36 @@ function cmdPreCompact() {
|
|
|
75
135
|
bootstrapFile,
|
|
76
136
|
host: HOST,
|
|
77
137
|
});
|
|
138
|
+
appendReplayEvent(cwd, {
|
|
139
|
+
host: HOST,
|
|
140
|
+
event: 'pre_compact_snapshot',
|
|
141
|
+
source: 'pre-compact',
|
|
142
|
+
details: {
|
|
143
|
+
bootstrapFile,
|
|
144
|
+
installMode: settings.install_mode || '',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
78
147
|
suppressedOutput(EVENT_NAME.PreCompact, context);
|
|
79
148
|
}
|
|
80
149
|
|
|
81
150
|
function cmdRoute() {
|
|
82
151
|
const payload = readStdinJson();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
? ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。适用条件:仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前上下文中已注入的“当前用户设置”,只有上下文不存在该信息时才尝试读取 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式,或当前项目已存在 .helloagents/(例如执行过 ~wiki 或 ~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();
|
|
152
|
+
clearTurnState(payload.cwd || process.cwd());
|
|
153
|
+
handleRouteCommand({
|
|
154
|
+
payload,
|
|
155
|
+
host: HOST,
|
|
156
|
+
pkgRoot: PKG_ROOT,
|
|
157
|
+
settings: getSettings(),
|
|
158
|
+
buildRouteInstruction,
|
|
159
|
+
buildSemanticRouteInstruction,
|
|
160
|
+
resolveCanonicalCommandSkill,
|
|
161
|
+
writeRouteContext,
|
|
162
|
+
clearRouteContext,
|
|
163
|
+
appendReplayEvent,
|
|
164
|
+
getWorkflowRecommendation,
|
|
165
|
+
suppress: (context) => suppressedOutput(EVENT_NAME.UserPromptSubmit, context),
|
|
166
|
+
emptySuppress,
|
|
167
|
+
});
|
|
122
168
|
}
|
|
123
169
|
|
|
124
170
|
function cmdInject() {
|
|
@@ -126,13 +172,29 @@ function cmdInject() {
|
|
|
126
172
|
const source = payload.source || 'startup';
|
|
127
173
|
const cwd = payload.cwd || process.cwd();
|
|
128
174
|
const settings = getSettings();
|
|
129
|
-
const bootstrapFile = resolveBootstrapFile(cwd, settings);
|
|
175
|
+
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode);
|
|
130
176
|
|
|
131
177
|
let bootstrap = '';
|
|
132
178
|
try {
|
|
133
179
|
bootstrap = readFileSync(join(PKG_ROOT, bootstrapFile), 'utf-8');
|
|
134
180
|
} catch {}
|
|
135
181
|
|
|
182
|
+
startReplaySession(cwd, {
|
|
183
|
+
host: HOST,
|
|
184
|
+
source,
|
|
185
|
+
bootstrapFile,
|
|
186
|
+
installMode: settings.install_mode || '',
|
|
187
|
+
});
|
|
188
|
+
appendReplayEvent(cwd, {
|
|
189
|
+
host: HOST,
|
|
190
|
+
event: 'session_injected',
|
|
191
|
+
source,
|
|
192
|
+
details: {
|
|
193
|
+
bootstrapFile,
|
|
194
|
+
installMode: settings.install_mode || '',
|
|
195
|
+
activatedProject: existsSync(join(cwd, '.helloagents')),
|
|
196
|
+
},
|
|
197
|
+
});
|
|
136
198
|
const context = buildInjectContext({
|
|
137
199
|
source,
|
|
138
200
|
bootstrap,
|
|
@@ -141,22 +203,31 @@ function cmdInject() {
|
|
|
141
203
|
host: HOST,
|
|
142
204
|
cwd,
|
|
143
205
|
});
|
|
206
|
+
clearRouteContext();
|
|
207
|
+
clearTurnState(cwd);
|
|
144
208
|
suppressedOutput(EVENT_NAME.SessionStart, context || undefined);
|
|
145
|
-
versionCheckBackground();
|
|
146
209
|
}
|
|
147
210
|
|
|
148
211
|
function cmdStop() {
|
|
149
212
|
const payload = readStdinJson();
|
|
213
|
+
const lastMsg = readCompletionText(payload);
|
|
214
|
+
const cwd = payload.cwd || process.cwd();
|
|
215
|
+
clearRouteContext();
|
|
150
216
|
if (runRalphLoop(payload)) {
|
|
151
217
|
playSound('warning');
|
|
152
|
-
desktopNotify('warning');
|
|
218
|
+
desktopNotify('warning', buildNotifyExtra(payload));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (shouldRunDeliveryGate(cwd, lastMsg) && runDeliveryGate(payload)) {
|
|
222
|
+
playSound('warning');
|
|
223
|
+
desktopNotify('warning', buildNotifyExtra(payload));
|
|
153
224
|
return;
|
|
154
225
|
}
|
|
155
226
|
|
|
156
227
|
const settings = getSettings();
|
|
157
228
|
const level = settings.notify_level ?? 0;
|
|
158
229
|
if (level === 2 || level === 3) playSound('complete');
|
|
159
|
-
if (level === 1 || level === 3) desktopNotify('complete');
|
|
230
|
+
if (level === 1 || level === 3) desktopNotify('complete', buildNotifyExtra(payload));
|
|
160
231
|
emptySuppress();
|
|
161
232
|
}
|
|
162
233
|
|
|
@@ -165,7 +236,7 @@ function cmdSound() {
|
|
|
165
236
|
}
|
|
166
237
|
|
|
167
238
|
function cmdDesktop() {
|
|
168
|
-
desktopNotify(process.argv[3] || 'complete');
|
|
239
|
+
desktopNotify(process.argv[3] || 'complete', buildNotifyExtra({ cwd: process.cwd() }));
|
|
169
240
|
}
|
|
170
241
|
|
|
171
242
|
function cmdCodexNotify() {
|
|
@@ -178,30 +249,32 @@ function cmdCodexNotify() {
|
|
|
178
249
|
|
|
179
250
|
if (type === 'approval-requested') {
|
|
180
251
|
playSound('confirm');
|
|
181
|
-
desktopNotify('confirm');
|
|
252
|
+
desktopNotify('confirm', buildNotifyExtra(data));
|
|
182
253
|
return;
|
|
183
254
|
}
|
|
184
255
|
if (type !== 'agent-turn-complete') return;
|
|
185
256
|
|
|
186
|
-
const lastMsg = data['last-assistant-message'] || '';
|
|
187
|
-
const settings = getSettings();
|
|
188
|
-
if (shouldIgnoreFormattedSubagent(lastMsg, settings.output_format !== false)) return;
|
|
189
|
-
|
|
190
257
|
const cwd = data.cwd || process.cwd();
|
|
191
|
-
|
|
258
|
+
const turnState = readTurnState(cwd);
|
|
259
|
+
if (!turnState || turnState.role !== 'main') return;
|
|
260
|
+
|
|
261
|
+
const settings = getSettings();
|
|
262
|
+
if (turnState.kind === 'complete' && runRalphLoop({ cwd })) {
|
|
192
263
|
playSound('warning');
|
|
193
|
-
desktopNotify('warning');
|
|
264
|
+
desktopNotify('warning', buildNotifyExtra(data));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (turnState.kind === 'complete' && runDeliveryGate({ cwd })) {
|
|
268
|
+
playSound('warning');
|
|
269
|
+
desktopNotify('warning', buildNotifyExtra(data));
|
|
194
270
|
return;
|
|
195
271
|
}
|
|
196
272
|
|
|
197
273
|
const level = settings.notify_level ?? 0;
|
|
198
274
|
if (level === 2 || level === 3) playSound('complete');
|
|
199
|
-
if (level === 1 || level === 3) desktopNotify('complete');
|
|
200
|
-
versionCheckBackground();
|
|
275
|
+
if (level === 1 || level === 3) desktopNotify('complete', buildNotifyExtra(data));
|
|
201
276
|
}
|
|
202
277
|
|
|
203
|
-
function cmdVersionCheck() {}
|
|
204
|
-
|
|
205
278
|
const cmd = process.argv[2] || '';
|
|
206
279
|
|
|
207
280
|
switch (cmd) {
|
|
@@ -212,7 +285,6 @@ switch (cmd) {
|
|
|
212
285
|
case 'sound': cmdSound(); break;
|
|
213
286
|
case 'desktop': cmdDesktop(); break;
|
|
214
287
|
case 'codex-notify': cmdCodexNotify(); break;
|
|
215
|
-
case 'version-check': cmdVersionCheck(); break;
|
|
216
288
|
default:
|
|
217
289
|
process.stderr.write(`notify.mjs: unknown command "${cmd}"\n`);
|
|
218
290
|
process.exit(1);
|