oh-my-codex 0.17.2 → 0.18.0
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/Cargo.lock +13 -5
- package/Cargo.toml +2 -1
- package/README.md +1 -0
- package/crates/omx-api/Cargo.toml +19 -0
- package/crates/omx-api/src/lib.rs +2940 -0
- package/crates/omx-api/src/main.rs +10 -0
- package/crates/omx-api/tests/cli.rs +558 -0
- package/crates/omx-explore/src/main.rs +4 -0
- package/crates/omx-sparkshell/src/codex_bridge.rs +437 -123
- package/crates/omx-sparkshell/src/exec.rs +4 -0
- package/crates/omx-sparkshell/src/main.rs +738 -29
- package/crates/omx-sparkshell/src/prompt.rs +25 -3
- package/crates/omx-sparkshell/src/redaction.rs +241 -0
- package/crates/omx-sparkshell/tests/execution.rs +479 -238
- package/dist/cli/__tests__/api.test.d.ts +2 -0
- package/dist/cli/__tests__/api.test.d.ts.map +1 -0
- package/dist/cli/__tests__/api.test.js +175 -0
- package/dist/cli/__tests__/api.test.js.map +1 -0
- package/dist/cli/__tests__/ask.test.js +72 -5
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js +14 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +51 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +23 -0
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +123 -5
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +76 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +45 -22
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +2 -0
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +138 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +8 -2
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +5 -0
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
- package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/api.d.ts +26 -0
- package/dist/cli/api.d.ts.map +1 -0
- package/dist/cli/api.js +153 -0
- package/dist/cli/api.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +39 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts +2 -0
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +43 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +10 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +128 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +2 -1
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +1 -0
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +6 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +20 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +90 -0
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +11 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +2 -2
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +23 -18
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +7 -6
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +75 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +3 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +71 -2
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +737 -26
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +4 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/build-api.d.ts +2 -0
- package/dist/scripts/build-api.d.ts.map +1 -0
- package/dist/scripts/build-api.js +44 -0
- package/dist/scripts/build-api.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +208 -8
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +89 -24
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +88 -0
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +27 -9
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +26 -11
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -0
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +38 -0
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +27 -14
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +9 -3
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +1 -1
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +2 -0
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +2 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +153 -25
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +55 -10
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/utils/__tests__/agents-md.test.js +45 -1
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts +2 -0
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +19 -0
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/package.json +4 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +1 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/prompts/researcher.md +15 -10
- package/skills/best-practice-research/SKILL.md +83 -0
- package/skills/deep-interview/SKILL.md +1 -0
- package/skills/ralplan/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +810 -4
- package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
- package/src/scripts/__tests__/smoke-packed-install.test.ts +8 -2
- package/src/scripts/build-api.ts +48 -0
- package/src/scripts/codex-native-hook.ts +262 -10
- package/src/scripts/codex-native-pre-post.ts +103 -24
- package/src/scripts/notify-dispatcher.ts +97 -0
- package/src/scripts/notify-hook/team-dispatch.ts +27 -8
- package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
- package/src/scripts/notify-hook/team-tmux-guard.ts +42 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
- package/src/scripts/run-provider-advisor.ts +11 -3
- package/src/scripts/smoke-packed-install.ts +2 -0
- package/templates/catalog-manifest.json +7 -0
|
@@ -103,8 +103,8 @@ export function normalizePostToolUsePayload(
|
|
|
103
103
|
const exitCode = safeInteger(parsedToolResponse?.exit_code)
|
|
104
104
|
?? safeInteger(parsedToolResponse?.exitCode)
|
|
105
105
|
?? null;
|
|
106
|
-
const
|
|
107
|
-
const stdoutText = safeString(parsedToolResponse?.stdout).trim() ||
|
|
106
|
+
const rawToolResponseText = safeString(rawToolResponse).trim();
|
|
107
|
+
const stdoutText = safeString(parsedToolResponse?.stdout).trim() || rawToolResponseText;
|
|
108
108
|
const stderrText = safeString(parsedToolResponse?.stderr).trim();
|
|
109
109
|
|
|
110
110
|
return {
|
|
@@ -149,30 +149,49 @@ type OmxParityCommand =
|
|
|
149
149
|
| "trace"
|
|
150
150
|
| "code-intel";
|
|
151
151
|
|
|
152
|
+
function joinNonEmptyText(parts: string[]): string {
|
|
153
|
+
return parts
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join("\n")
|
|
156
|
+
.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function structuredMcpTransportText(normalized: NormalizedPostToolUsePayload): string {
|
|
160
|
+
return joinNonEmptyText([
|
|
161
|
+
safeString(normalized.parsedToolResponse?.error),
|
|
162
|
+
safeString(normalized.parsedToolResponse?.message),
|
|
163
|
+
safeString(normalized.parsedToolResponse?.details),
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function hasMcpTransportContext(text: string): boolean {
|
|
168
|
+
return /\bmcp\b/i.test(text)
|
|
169
|
+
|| /\bomx-(?:state|memory|trace|code-intel)-server\b/i.test(text);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasMcpTransportFailurePattern(text: string): boolean {
|
|
173
|
+
return MCP_TRANSPORT_FAILURE_PATTERNS.some((pattern) => pattern.test(text));
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
export function detectMcpTransportFailure(
|
|
153
177
|
payload: CodexHookPayload,
|
|
154
178
|
): McpTransportFailureSignal | null {
|
|
155
179
|
const normalized = normalizePostToolUsePayload(payload);
|
|
156
180
|
if (normalized.isBash) return null;
|
|
157
|
-
|
|
181
|
+
|
|
182
|
+
const isMcpTool = isMcpLikeToolName(normalized.toolName);
|
|
183
|
+
const structuredText = structuredMcpTransportText(normalized);
|
|
184
|
+
const rawText = joinNonEmptyText([
|
|
158
185
|
normalized.stderrText,
|
|
159
186
|
normalized.stdoutText,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.filter(Boolean)
|
|
165
|
-
.join("\n")
|
|
166
|
-
.trim();
|
|
187
|
+
]);
|
|
188
|
+
const combined = isMcpTool
|
|
189
|
+
? joinNonEmptyText([rawText, structuredText])
|
|
190
|
+
: structuredText;
|
|
167
191
|
|
|
168
|
-
const mcpContextDetected = isMcpLikeToolName(normalized.toolName)
|
|
169
|
-
|| /\bmcp\b/i.test(combined)
|
|
170
|
-
|| /\bomx-(?:state|memory|trace|code-intel)-server\b/i.test(combined);
|
|
171
|
-
if (!mcpContextDetected) return null;
|
|
172
192
|
if (!combined) return null;
|
|
173
|
-
if (!
|
|
174
|
-
|
|
175
|
-
}
|
|
193
|
+
if (!isMcpTool && !hasMcpTransportContext(structuredText)) return null;
|
|
194
|
+
if (!hasMcpTransportFailurePattern(combined)) return null;
|
|
176
195
|
|
|
177
196
|
return {
|
|
178
197
|
toolName: normalized.toolName,
|
|
@@ -976,15 +995,75 @@ function buildSloppyFallbackPreToolUseOutput(commandText: string): Record<string
|
|
|
976
995
|
};
|
|
977
996
|
}
|
|
978
997
|
|
|
998
|
+
function removeHereDocBodies(command: string): string {
|
|
999
|
+
const lines = command.split(/\r?\n/);
|
|
1000
|
+
const retained: string[] = [];
|
|
1001
|
+
let pendingDelimiter: string | null = null;
|
|
1002
|
+
|
|
1003
|
+
for (const line of lines) {
|
|
1004
|
+
if (pendingDelimiter) {
|
|
1005
|
+
if (line.trim() === pendingDelimiter) {
|
|
1006
|
+
pendingDelimiter = null;
|
|
1007
|
+
}
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
retained.push(line);
|
|
1012
|
+
const match = /<<-?\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_.-]+))/.exec(line);
|
|
1013
|
+
if (match) pendingDelimiter = match[1] || match[2] || match[3] || null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return retained.join("\n");
|
|
1017
|
+
}
|
|
1018
|
+
|
|
979
1019
|
function commandInvokesOmxQuestion(command: string): boolean {
|
|
980
|
-
const tokens =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1020
|
+
const tokens = tokenizeShellCommandWithBoundaries(removeHereDocBodies(command))
|
|
1021
|
+
?.map((token) => ({ ...token, value: token.value.toLowerCase() }))
|
|
1022
|
+
?? [];
|
|
1023
|
+
|
|
1024
|
+
for (let commandStart = 0; commandStart < tokens.length; commandStart = nextCommandStart(tokens, commandStart)) {
|
|
1025
|
+
const commandEnd = nextCommandStart(tokens, commandStart);
|
|
1026
|
+
let index = commandStart;
|
|
1027
|
+
|
|
1028
|
+
while (index < commandEnd && isInlineShellEnvAssignment(tokens[index]?.value ?? "")) {
|
|
1029
|
+
index += 1;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
while (index < commandEnd && isEnvExecutableToken(tokens[index]?.value ?? "")) {
|
|
1033
|
+
index += 1;
|
|
1034
|
+
while (index < commandEnd) {
|
|
1035
|
+
const token = tokens[index]?.value ?? "";
|
|
1036
|
+
if (token === "--") {
|
|
1037
|
+
index += 1;
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
if (isInlineShellEnvAssignment(token)) {
|
|
1041
|
+
index += 1;
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if (token === "-i" || token === "--ignore-environment" || token.startsWith("--unset=")) {
|
|
1045
|
+
index += 1;
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (token.startsWith("-")) {
|
|
1049
|
+
index += envOptionConsumesNextValue(token) ? 2 : 1;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const rawToken = tokens[index]?.value || "";
|
|
1057
|
+
const token = rawToken.replace(/\\/g, "/").split("/").pop() || "";
|
|
1058
|
+
if ((token === "omx" || token === "omx.js") && tokens[index + 1]?.value === "question") return true;
|
|
1059
|
+
if (
|
|
1060
|
+
(token === "node" || token === "node.exe")
|
|
1061
|
+
&& /(?:^|\/)omx\.js$/.test(tokens[index + 1]?.value || "")
|
|
1062
|
+
&& tokens[index + 2]?.value === "question"
|
|
1063
|
+
) return true;
|
|
986
1064
|
}
|
|
987
|
-
|
|
1065
|
+
|
|
1066
|
+
return false;
|
|
988
1067
|
}
|
|
989
1068
|
|
|
990
1069
|
function isQuestionReturnPaneAssignment(token: string): boolean {
|
|
@@ -52,6 +52,21 @@ function resolveNotifyEntrypoint(command: readonly string[]): string | undefined
|
|
|
52
52
|
return command.slice(1).find((arg) => !arg.startsWith("-"));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function getPreviousNotifyWrapperValue(
|
|
56
|
+
command: readonly string[],
|
|
57
|
+
): string | undefined {
|
|
58
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
59
|
+
const part = command[index];
|
|
60
|
+
if (part === "--previous-notify") {
|
|
61
|
+
return command[index + 1];
|
|
62
|
+
}
|
|
63
|
+
if (part.startsWith("--previous-notify=")) {
|
|
64
|
+
return part.slice("--previous-notify=".length);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
function isOmxManagedNotifyCommand(command: readonly string[] | null | undefined): boolean {
|
|
56
71
|
if (!command) return false;
|
|
57
72
|
const entrypoint = resolveNotifyEntrypoint(command);
|
|
@@ -62,12 +77,94 @@ function isOmxManagedNotifyCommand(command: readonly string[] | null | undefined
|
|
|
62
77
|
return /(?:^|[\\/])oh-my-codex(?:[\\/]|$)/.test(entrypoint);
|
|
63
78
|
}
|
|
64
79
|
|
|
80
|
+
function isOmxDispatcherMetadataCommand(command: readonly string[] | null | undefined): boolean {
|
|
81
|
+
if (!command) return false;
|
|
82
|
+
const entrypoint = resolveNotifyEntrypoint(command);
|
|
83
|
+
if (!entrypoint || !/(?:^|[\\/])notify-dispatcher\.js$/.test(entrypoint)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const metadataIndex = command.indexOf("--metadata");
|
|
87
|
+
const metadataPath = metadataIndex >= 0 ? command[metadataIndex + 1] : undefined;
|
|
88
|
+
return typeof metadataPath === "string" && /(?:^|[\\/])(?:\.omx[\\/])?notify-dispatch\.json$/.test(metadataPath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isOmxManagedPayloadText(value: string): boolean {
|
|
92
|
+
const containsManagedPackageNotify =
|
|
93
|
+
/(?:^|[\\/])notify-(?:hook|dispatcher)\.js(?:\s|$|["'])/.test(
|
|
94
|
+
value,
|
|
95
|
+
) && /(?:^|[\\/])oh-my-codex(?:[\\/]|$)/.test(value);
|
|
96
|
+
const containsDispatcherMetadataNotify =
|
|
97
|
+
/(?:^|[\\/])notify-dispatcher\.js(?:\s|$|["'])/.test(value) &&
|
|
98
|
+
/--metadata(?:\s|=)/.test(value) &&
|
|
99
|
+
/(?:^|[\\/])(?:\.omx[\\/])?notify-dispatch\.json(?:\s|$|["'])/.test(value);
|
|
100
|
+
return containsManagedPackageNotify || containsDispatcherMetadataNotify;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseJsonString(value: string): unknown | undefined {
|
|
104
|
+
const trimmed = value.trim();
|
|
105
|
+
if (!trimmed) return undefined;
|
|
106
|
+
const first = trimmed[0];
|
|
107
|
+
if (first !== "[" && first !== "{" && first !== '"') return undefined;
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(trimmed) as unknown;
|
|
110
|
+
} catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function containsOmxManagedNotifyPayload(value: unknown, depth = 0): boolean {
|
|
116
|
+
if (depth > 8 || value == null) return false;
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
const parsed = parseJsonString(value);
|
|
119
|
+
if (parsed !== undefined && parsed !== value) {
|
|
120
|
+
return containsOmxManagedNotifyPayload(parsed, depth + 1);
|
|
121
|
+
}
|
|
122
|
+
return isOmxManagedPayloadText(value);
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
if (value.every((item) => typeof item === "string")) {
|
|
126
|
+
const command = value as string[];
|
|
127
|
+
return (
|
|
128
|
+
isOmxManagedNotifyCommand(command) ||
|
|
129
|
+
isOmxDispatcherMetadataCommand(command) ||
|
|
130
|
+
isOmxManagedPreviousNotifyWrapper(command)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return value.some((item) => containsOmxManagedNotifyPayload(item, depth + 1));
|
|
134
|
+
}
|
|
135
|
+
if (typeof value === "object") {
|
|
136
|
+
const record = value as Record<string, unknown>;
|
|
137
|
+
return [
|
|
138
|
+
record.previousNotify,
|
|
139
|
+
record.previous_notify,
|
|
140
|
+
record.notify,
|
|
141
|
+
record.command,
|
|
142
|
+
record.argv,
|
|
143
|
+
record.args,
|
|
144
|
+
].some((item) => containsOmxManagedNotifyPayload(item, depth + 1));
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isOmxManagedPreviousNotifyWrapper(
|
|
150
|
+
command: readonly string[] | null | undefined,
|
|
151
|
+
): boolean {
|
|
152
|
+
if (!command) return false;
|
|
153
|
+
if (!command.some((part) => part === "turn-ended")) return false;
|
|
154
|
+
const previousNotify = getPreviousNotifyWrapperValue(command);
|
|
155
|
+
if (!previousNotify) return false;
|
|
156
|
+
|
|
157
|
+
return containsOmxManagedNotifyPayload(previousNotify);
|
|
158
|
+
}
|
|
159
|
+
|
|
65
160
|
function isManagedPreviousNotify(
|
|
66
161
|
previousNotify: readonly string[] | null | undefined,
|
|
67
162
|
metadata: NotifyDispatcherMetadata | null,
|
|
68
163
|
): boolean {
|
|
69
164
|
return (
|
|
70
165
|
isOmxManagedNotifyCommand(previousNotify) ||
|
|
166
|
+
isOmxDispatcherMetadataCommand(previousNotify) ||
|
|
167
|
+
isOmxManagedPreviousNotifyWrapper(previousNotify) ||
|
|
71
168
|
sameCommand(previousNotify, metadata?.omxNotify) ||
|
|
72
169
|
sameCommand(previousNotify, metadata?.dispatcherNotify)
|
|
73
170
|
);
|
|
@@ -726,12 +726,14 @@ function resolveWorkerCliForRequest(request, config) {
|
|
|
726
726
|
|
|
727
727
|
function capturedPaneContainsTrigger(captured, trigger) {
|
|
728
728
|
if (!captured || !trigger) return false;
|
|
729
|
-
|
|
729
|
+
const normalizeForDraftMatch = (value) => normalizeTmuxCapture(value).replace(/-\s+/g, '-');
|
|
730
|
+
return normalizeForDraftMatch(captured).includes(normalizeForDraftMatch(trigger));
|
|
730
731
|
}
|
|
731
732
|
|
|
732
733
|
function capturedPaneContainsTriggerNearTail(captured, trigger, nonEmptyTailLines = 24) {
|
|
733
734
|
if (!captured || !trigger) return false;
|
|
734
|
-
const
|
|
735
|
+
const normalizeForDraftMatch = (value) => normalizeTmuxCapture(value).replace(/-\s+/g, '-');
|
|
736
|
+
const normalizedTrigger = normalizeForDraftMatch(trigger);
|
|
735
737
|
if (!normalizedTrigger) return false;
|
|
736
738
|
const lines = safeString(captured)
|
|
737
739
|
.split('\n')
|
|
@@ -739,7 +741,13 @@ function capturedPaneContainsTriggerNearTail(captured, trigger, nonEmptyTailLine
|
|
|
739
741
|
.filter((line) => line.length > 0);
|
|
740
742
|
if (lines.length === 0) return false;
|
|
741
743
|
const tail = lines.slice(-Math.max(1, nonEmptyTailLines)).join(' ');
|
|
742
|
-
return
|
|
744
|
+
return normalizeForDraftMatch(tail).includes(normalizedTrigger);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function buildJoinedCapturePaneArgv(paneTarget, tailLines = 80) {
|
|
748
|
+
// Join wrapped visual lines so long path-like trigger text split by tmux
|
|
749
|
+
// remains comparable with the original trigger.
|
|
750
|
+
return ['capture-pane', '-J', '-t', paneTarget, '-p', '-S', `-${tailLines}`];
|
|
743
751
|
}
|
|
744
752
|
|
|
745
753
|
const INJECT_VERIFY_DELAY_MS = 250;
|
|
@@ -791,7 +799,7 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
|
|
|
791
799
|
if (attemptCountAtStart >= 1) {
|
|
792
800
|
try {
|
|
793
801
|
// Narrow capture (8 lines) to scope check to input area, not scrollback output
|
|
794
|
-
const preCapture = await runProcess('tmux',
|
|
802
|
+
const preCapture = await runProcess('tmux', buildJoinedCapturePaneArgv(resolution.paneTarget, 8), 2000);
|
|
795
803
|
preCaptureHasTrigger = capturedPaneContainsTrigger(preCapture.stdout, request.trigger_message);
|
|
796
804
|
} catch {
|
|
797
805
|
preCaptureHasTrigger = false;
|
|
@@ -830,8 +838,8 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
|
|
|
830
838
|
// Post-injection verification: confirm the trigger text was consumed.
|
|
831
839
|
// Fixes #391: without this, dispatch marks 'notified' even when the worker
|
|
832
840
|
// pane is sitting on an unsent draft (C-m was not effectively applied).
|
|
833
|
-
const verifyNarrowArgv =
|
|
834
|
-
const verifyWideArgv =
|
|
841
|
+
const verifyNarrowArgv = buildJoinedCapturePaneArgv(resolution.paneTarget, 8);
|
|
842
|
+
const verifyWideArgv = buildJoinedCapturePaneArgv(resolution.paneTarget);
|
|
835
843
|
for (let round = 0; round < INJECT_VERIFY_ROUNDS; round++) {
|
|
836
844
|
await new Promise((r) => setTimeout(r, INJECT_VERIFY_DELAY_MS));
|
|
837
845
|
try {
|
|
@@ -842,6 +850,19 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
|
|
|
842
850
|
// full-scrollback false positives.
|
|
843
851
|
const narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
|
|
844
852
|
const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
|
|
853
|
+
const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
|
|
854
|
+
const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
|
|
855
|
+
if (triggerInNarrow || triggerNearTail) {
|
|
856
|
+
// Draft is still visible, so C-m has not actually submitted it yet.
|
|
857
|
+
// Do not let transient spinner/active-task text mask an unsent draft.
|
|
858
|
+
await sendPaneInput({
|
|
859
|
+
paneTarget: resolution.paneTarget,
|
|
860
|
+
prompt: request.trigger_message,
|
|
861
|
+
submitKeyPresses,
|
|
862
|
+
typePrompt: false,
|
|
863
|
+
}).catch(() => {});
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
845
866
|
// Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
|
|
846
867
|
if (paneHasActiveTask(wideCap.stdout)) {
|
|
847
868
|
runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
|
|
@@ -862,8 +883,6 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
|
|
|
862
883
|
if (request.to_worker !== 'leader-fixed' && !paneLooksReady(wideCap.stdout)) {
|
|
863
884
|
continue;
|
|
864
885
|
}
|
|
865
|
-
const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
|
|
866
|
-
const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
|
|
867
886
|
if (!triggerInNarrow && !triggerNearTail) {
|
|
868
887
|
runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
|
|
869
888
|
return {
|
|
@@ -11,14 +11,14 @@ import { asNumber, safeString, isTerminalPhase } from './utils.js';
|
|
|
11
11
|
import { readJsonIfExists, getScopedStateDirsForCurrentSession } from './state-io.js';
|
|
12
12
|
import { runProcess } from './process-runner.js';
|
|
13
13
|
import { logTmuxHookEvent } from './log.js';
|
|
14
|
-
import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
|
|
14
|
+
import { evaluatePaneInjectionReadiness, queuePaneInput, sendPaneInput } from './team-tmux-guard.js';
|
|
15
15
|
import { resolvePaneTarget } from './tmux-injection.js';
|
|
16
16
|
import { listNotifyCanonicalActiveTeams } from './active-team.js';
|
|
17
17
|
import {
|
|
18
18
|
classifyLeaderActionState,
|
|
19
19
|
resolveLeaderNudgeIntent,
|
|
20
20
|
} from './orchestration-intent.js';
|
|
21
|
-
import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
|
|
21
|
+
import { DEFAULT_MARKER, paneHasActiveTask } from '../tmux-hook-engine.js';
|
|
22
22
|
import { isLeaderRuntimeStale } from '../../team/leader-activity.js';
|
|
23
23
|
import { appendTeamDeliveryLog } from '../../team/delivery-log.js';
|
|
24
24
|
import { writeTeamLeaderAttention } from '../../team/state.js';
|
|
@@ -967,14 +967,27 @@ export async function maybeNudgeTeamLeader({
|
|
|
967
967
|
}
|
|
968
968
|
|
|
969
969
|
try {
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
970
|
+
const leaderHasActiveTask = paneHasActiveTask(paneGuard.paneCapture);
|
|
971
|
+
let deliveryMode = 'sent';
|
|
972
|
+
if (leaderHasActiveTask) {
|
|
973
|
+
const sendResult = await queuePaneInput({
|
|
974
|
+
paneTarget: tmuxTarget,
|
|
975
|
+
prompt: markedText,
|
|
976
|
+
});
|
|
977
|
+
if (!sendResult.ok) {
|
|
978
|
+
throw new Error(sendResult.error || sendResult.reason);
|
|
979
|
+
}
|
|
980
|
+
deliveryMode = 'queued';
|
|
981
|
+
} else {
|
|
982
|
+
const sendResult = await sendPaneInput({
|
|
983
|
+
paneTarget: tmuxTarget,
|
|
984
|
+
prompt: markedText,
|
|
985
|
+
submitKeyPresses: 2,
|
|
986
|
+
submitDelayMs: 100,
|
|
987
|
+
});
|
|
988
|
+
if (!sendResult.ok) {
|
|
989
|
+
throw new Error(sendResult.error || sendResult.reason);
|
|
990
|
+
}
|
|
978
991
|
}
|
|
979
992
|
nudgeState.last_nudged_by_team[teamName] = {
|
|
980
993
|
at: nowIso,
|
|
@@ -1005,6 +1018,7 @@ export async function maybeNudgeTeamLeader({
|
|
|
1005
1018
|
message_count: messages.length,
|
|
1006
1019
|
stalled_for_ms: undefined,
|
|
1007
1020
|
missing_signal_workers: progressSnapshot.missingSignalWorkers,
|
|
1021
|
+
delivery: deliveryMode,
|
|
1008
1022
|
});
|
|
1009
1023
|
} catch { /* ignore */ }
|
|
1010
1024
|
await appendTeamDeliveryLog(logsDir, {
|
|
@@ -1013,7 +1027,7 @@ export async function maybeNudgeTeamLeader({
|
|
|
1013
1027
|
team: teamName,
|
|
1014
1028
|
to_worker: 'leader-fixed',
|
|
1015
1029
|
transport: 'send-keys',
|
|
1016
|
-
result:
|
|
1030
|
+
result: deliveryMode,
|
|
1017
1031
|
reason: nudgeReason,
|
|
1018
1032
|
orchestration_intent: orchestrationIntent,
|
|
1019
1033
|
}).catch(() => {});
|
|
@@ -182,6 +182,48 @@ export async function sendPaneInput({
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
export async function queuePaneInput({
|
|
186
|
+
paneTarget,
|
|
187
|
+
prompt,
|
|
188
|
+
submitDelayMs = 80,
|
|
189
|
+
}: any): Promise<any> {
|
|
190
|
+
const sendResult = await sendPaneInput({
|
|
191
|
+
paneTarget,
|
|
192
|
+
prompt,
|
|
193
|
+
submitKeyPresses: 0,
|
|
194
|
+
});
|
|
195
|
+
if (!sendResult.ok) return sendResult;
|
|
196
|
+
|
|
197
|
+
const target = safeString(paneTarget).trim();
|
|
198
|
+
const submitArgv = [
|
|
199
|
+
['send-keys', '-t', target, 'Tab'],
|
|
200
|
+
['send-keys', '-t', target, 'C-m'],
|
|
201
|
+
];
|
|
202
|
+
try {
|
|
203
|
+
await runProcess('tmux', submitArgv[0], 3000);
|
|
204
|
+
if (submitDelayMs > 0) {
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, submitDelayMs));
|
|
206
|
+
}
|
|
207
|
+
await runProcess('tmux', submitArgv[1], 3000);
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
sent: true,
|
|
211
|
+
reason: 'queued',
|
|
212
|
+
paneTarget: target,
|
|
213
|
+
argv: { typeArgv: sendResult.argv?.typeArgv || null, submitArgv },
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
sent: false,
|
|
219
|
+
reason: 'queue_failed',
|
|
220
|
+
paneTarget: target,
|
|
221
|
+
argv: { typeArgv: sendResult.argv?.typeArgv || null, submitArgv },
|
|
222
|
+
error: error instanceof Error ? error.message : safeString(error),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
185
227
|
export async function checkPaneReadyForTeamSendKeys(paneTarget: any): Promise<any> {
|
|
186
228
|
return evaluatePaneInjectionReadiness(paneTarget);
|
|
187
229
|
}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import { appendFile, mkdir, rename, writeFile } from 'fs/promises';
|
|
10
10
|
import { dirname, join } from 'path';
|
|
11
|
-
import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
|
|
11
|
+
import { DEFAULT_MARKER, paneHasActiveTask } from '../tmux-hook-engine.js';
|
|
12
12
|
import { appendTeamDeliveryLog } from '../../team/delivery-log.js';
|
|
13
13
|
import { safeString, asNumber } from './utils.js';
|
|
14
14
|
import { readJsonIfExists } from './state-io.js';
|
|
15
15
|
import { logTmuxHookEvent } from './log.js';
|
|
16
|
-
import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
|
|
16
|
+
import { evaluatePaneInjectionReadiness, queuePaneInput, sendPaneInput } from './team-tmux-guard.js';
|
|
17
17
|
import { resolvePaneTarget } from './tmux-injection.js';
|
|
18
18
|
import { readTeamWorkersForIdleCheck } from './team-worker.js';
|
|
19
19
|
|
|
@@ -185,17 +185,28 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
|
|
|
185
185
|
+ DEFAULT_MARKER;
|
|
186
186
|
|
|
187
187
|
try {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
188
|
+
const leaderHasActiveTask = paneHasActiveTask(paneGuard.paneCapture);
|
|
189
|
+
let deliveryMode = 'sent';
|
|
190
|
+
if (leaderHasActiveTask) {
|
|
191
|
+
const sendResult = await queuePaneInput({
|
|
192
|
+
paneTarget: tmuxTarget,
|
|
193
|
+
prompt,
|
|
194
|
+
});
|
|
195
|
+
if (!sendResult.ok) throw new Error(sendResult.error || sendResult.reason || 'send_failed');
|
|
196
|
+
deliveryMode = 'queued';
|
|
197
|
+
} else {
|
|
198
|
+
const sendResult = await sendPaneInput({
|
|
199
|
+
paneTarget: tmuxTarget,
|
|
200
|
+
prompt,
|
|
201
|
+
submitKeyPresses: 2,
|
|
202
|
+
submitDelayMs: 100,
|
|
203
|
+
});
|
|
204
|
+
if (!sendResult.ok) throw new Error(sendResult.error || sendResult.reason || 'send_failed');
|
|
205
|
+
}
|
|
195
206
|
|
|
196
207
|
await writeStopNudgeState(statePath, {
|
|
197
208
|
...nextState,
|
|
198
|
-
delivery:
|
|
209
|
+
delivery: deliveryMode,
|
|
199
210
|
leader_pane_id: leaderPaneId || null,
|
|
200
211
|
tmux_target: tmuxTarget,
|
|
201
212
|
});
|
|
@@ -205,7 +216,7 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
|
|
|
205
216
|
type: 'worker_stop_leader_nudge',
|
|
206
217
|
worker: workerName,
|
|
207
218
|
to_worker: 'leader-fixed',
|
|
208
|
-
delivery:
|
|
219
|
+
delivery: deliveryMode,
|
|
209
220
|
created_at: nowIso,
|
|
210
221
|
source_type: SOURCE_TYPE,
|
|
211
222
|
});
|
|
@@ -225,10 +236,10 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
|
|
|
225
236
|
from_worker: workerName,
|
|
226
237
|
to_worker: 'leader-fixed',
|
|
227
238
|
transport: 'send-keys',
|
|
228
|
-
result:
|
|
239
|
+
result: deliveryMode,
|
|
229
240
|
reason: 'worker_stop_allowed',
|
|
230
241
|
}).catch(() => {});
|
|
231
|
-
return { ok: true, result:
|
|
242
|
+
return { ok: true, result: deliveryMode };
|
|
232
243
|
} catch (err) {
|
|
233
244
|
await recordDeferred({
|
|
234
245
|
stateDir,
|
|
@@ -83,6 +83,16 @@ function shouldUseClaudeIssuePermissionsBypass(provider: string, prompt: string)
|
|
|
83
83
|
return ISSUE_WORK_PROMPT_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function buildProviderLaunchArgs(provider: string, prompt: string, originalTask: string): string[] {
|
|
87
|
+
const promptArgs = provider === 'claude'
|
|
88
|
+
? ['-p', '--', prompt]
|
|
89
|
+
: ['-p', prompt];
|
|
90
|
+
|
|
91
|
+
return shouldUseClaudeIssuePermissionsBypass(provider, originalTask)
|
|
92
|
+
? [CLAUDE_SKIP_PERMISSIONS_FLAG, ...promptArgs]
|
|
93
|
+
: promptArgs;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
function buildSummary(exitCode: number, output: string): string {
|
|
87
97
|
if (exitCode === 0) {
|
|
88
98
|
return 'Provider completed successfully. Review the raw output for details.';
|
|
@@ -165,9 +175,7 @@ async function main(): Promise<void> {
|
|
|
165
175
|
|
|
166
176
|
ensureBinary(binary);
|
|
167
177
|
|
|
168
|
-
const launchArgs =
|
|
169
|
-
? [CLAUDE_SKIP_PERMISSIONS_FLAG, '-p', prompt]
|
|
170
|
-
: ['-p', prompt];
|
|
178
|
+
const launchArgs = buildProviderLaunchArgs(provider, prompt, originalTask);
|
|
171
179
|
|
|
172
180
|
const run = spawnSync(binary, launchArgs, {
|
|
173
181
|
encoding: 'utf8',
|
|
@@ -108,6 +108,13 @@
|
|
|
108
108
|
"core": false,
|
|
109
109
|
"internalRequired": false
|
|
110
110
|
},
|
|
111
|
+
{
|
|
112
|
+
"name": "best-practice-research",
|
|
113
|
+
"category": "planning",
|
|
114
|
+
"status": "active",
|
|
115
|
+
"core": false,
|
|
116
|
+
"internalRequired": false
|
|
117
|
+
},
|
|
111
118
|
{
|
|
112
119
|
"name": "analyze",
|
|
113
120
|
"category": "shortcut",
|