@sayknow-cli/coding-agent 0.3.7 → 0.3.8
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +14 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +23 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +92 -8
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +68 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +14 -5
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +180 -18
- package/src/modes/controllers/selector-controller.ts +127 -24
- package/src/modes/interactive-mode.ts +51 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config.ts +25 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +117 -14
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +302 -51
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +52 -28
- package/src/session/agent-session.ts +1011 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +218 -71
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -10,6 +10,29 @@ const ULTRAGOAL_ASK_GUARD = Symbol.for("sayknow-cli.ultragoalAskGuard");
|
|
|
10
10
|
|
|
11
11
|
type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
|
|
12
12
|
|
|
13
|
+
export interface UltragoalAskGuardContext {
|
|
14
|
+
activeSkillState?: { skill?: string; session_id?: string } | null;
|
|
15
|
+
sessionId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const UPSTREAM_PLANNING_ASK_SKILLS = new Set(["deep-interview", "ralplan"]);
|
|
19
|
+
|
|
20
|
+
function normalizedActiveSkill(context?: UltragoalAskGuardContext): string | undefined {
|
|
21
|
+
const skill = context?.activeSkillState?.skill?.trim();
|
|
22
|
+
return skill || undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sessionScopedAskGuardId(
|
|
26
|
+
context: UltragoalAskGuardContext,
|
|
27
|
+
activeSkill: string | undefined,
|
|
28
|
+
): string | undefined {
|
|
29
|
+
if (activeSkill !== "ultragoal" && !UPSTREAM_PLANNING_ASK_SKILLS.has(activeSkill ?? "")) return undefined;
|
|
30
|
+
const activeSessionId = context.activeSkillState?.session_id?.trim();
|
|
31
|
+
if (activeSessionId) return activeSessionId;
|
|
32
|
+
const sessionId = context.sessionId?.trim();
|
|
33
|
+
return sessionId || undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
|
|
14
37
|
return [
|
|
15
38
|
diagnostic.message,
|
|
@@ -18,15 +41,26 @@ export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiag
|
|
|
18
41
|
].join("\n");
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
export async function assertUltragoalAskAllowed(cwd: string): Promise<void> {
|
|
22
|
-
const
|
|
44
|
+
export async function assertUltragoalAskAllowed(cwd: string, context: UltragoalAskGuardContext = {}): Promise<void> {
|
|
45
|
+
const activeSkill = normalizedActiveSkill(context);
|
|
46
|
+
// Deep-interview and ralplan are upstream planning workflows whose core gates
|
|
47
|
+
// are `ask` calls. Scope their Ultragoal check to the current session so stale
|
|
48
|
+
// or ambiguous Ultragoal durable state from another session cannot hijack those
|
|
49
|
+
// prompts; same-session active Ultragoal state still falls through to the
|
|
50
|
+
// blocker/nudge checks below.
|
|
51
|
+
const sessionId = sessionScopedAskGuardId(context, activeSkill);
|
|
52
|
+
const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
|
|
23
53
|
if (!diagnostic.active) return;
|
|
24
|
-
const nudge = await consumeUltragoalAskNudge(cwd);
|
|
54
|
+
const nudge = await consumeUltragoalAskNudge(cwd, sessionId);
|
|
25
55
|
if (nudge.nudged) throw new ToolError(nudge.message);
|
|
26
56
|
throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
59
|
+
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
60
|
+
tool: T,
|
|
61
|
+
getCwd: () => string,
|
|
62
|
+
getContext: () => UltragoalAskGuardContext = () => ({}),
|
|
63
|
+
): T {
|
|
30
64
|
if (tool.name !== "ask") return tool;
|
|
31
65
|
const candidate = tool as GuardedTool;
|
|
32
66
|
if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
|
|
@@ -35,7 +69,7 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: (
|
|
|
35
69
|
if (prop === ULTRAGOAL_ASK_GUARD) return true;
|
|
36
70
|
if (prop !== "execute") return Reflect.get(target, prop, receiver);
|
|
37
71
|
return async (...args: unknown[]): Promise<unknown> => {
|
|
38
|
-
await assertUltragoalAskAllowed(getCwd());
|
|
72
|
+
await assertUltragoalAskAllowed(getCwd(), getContext());
|
|
39
73
|
return Reflect.apply(target.execute, target, args);
|
|
40
74
|
};
|
|
41
75
|
},
|
|
@@ -159,24 +159,32 @@ async function resolveMentionPath(
|
|
|
159
159
|
return best?.candidate.path ?? null;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Default inline cap for auto-read file mentions, deliberately below the
|
|
164
|
+
* read-tool cap (`DEFAULT_MAX_BYTES`, 50KB) so an incidental `@path` mention
|
|
165
|
+
* injects a smaller snippet than an explicit `read`. Overridable per call.
|
|
166
|
+
*/
|
|
167
|
+
export const DEFAULT_FILE_MENTION_INLINE_BYTES = 20 * 1024;
|
|
168
|
+
|
|
169
|
+
function buildTextOutput(textContent: string, maxInlineBytes?: number): { output: string; lineCount: number } {
|
|
170
|
+
const cap = maxInlineBytes && maxInlineBytes > 0 ? maxInlineBytes : DEFAULT_FILE_MENTION_INLINE_BYTES;
|
|
163
171
|
const allLines = textContent.split("\n");
|
|
164
172
|
const totalFileLines = allLines.length;
|
|
165
|
-
const truncation = truncateHead(textContent);
|
|
173
|
+
const truncation = truncateHead(textContent, { maxBytes: cap });
|
|
166
174
|
|
|
167
175
|
if (truncation.firstLineExceedsLimit) {
|
|
168
176
|
const firstLine = allLines[0] ?? "";
|
|
169
177
|
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
170
|
-
const snippet = truncateHeadBytes(firstLine,
|
|
178
|
+
const snippet = truncateHeadBytes(firstLine, cap);
|
|
171
179
|
let outputText = snippet.text;
|
|
172
180
|
|
|
173
181
|
if (outputText.length > 0) {
|
|
174
182
|
outputText += `\n\n[Line 1 is ${formatBytes(firstLineBytes)}, exceeds ${formatBytes(
|
|
175
|
-
|
|
183
|
+
cap,
|
|
176
184
|
)} limit. Showing first ${formatBytes(snippet.bytes)} of the line.]`;
|
|
177
185
|
} else {
|
|
178
186
|
outputText = `[Line 1 is ${formatBytes(firstLineBytes)}, exceeds ${formatBytes(
|
|
179
|
-
|
|
187
|
+
cap,
|
|
180
188
|
)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
181
189
|
}
|
|
182
190
|
|
|
@@ -277,7 +285,18 @@ export function extractFileMentions(text: string): string[] {
|
|
|
277
285
|
export async function generateFileMentionMessages(
|
|
278
286
|
filePaths: string[],
|
|
279
287
|
cwd: string,
|
|
280
|
-
options?: {
|
|
288
|
+
options?: {
|
|
289
|
+
autoResizeImages?: boolean;
|
|
290
|
+
useHashLines?: boolean;
|
|
291
|
+
/** Inline byte cap for auto-read text. Default: DEFAULT_FILE_MENTION_INLINE_BYTES. */
|
|
292
|
+
maxInlineBytes?: number;
|
|
293
|
+
/**
|
|
294
|
+
* Resolved paths already shown (read or mentioned) recently in the
|
|
295
|
+
* conversation. A mention of one of these emits a compact duplicate note
|
|
296
|
+
* instead of re-injecting the full body.
|
|
297
|
+
*/
|
|
298
|
+
recentlyShownPaths?: ReadonlySet<string>;
|
|
299
|
+
},
|
|
281
300
|
): Promise<AgentMessage[]> {
|
|
282
301
|
if (filePaths.length === 0) return [];
|
|
283
302
|
|
|
@@ -290,12 +309,25 @@ export async function generateFileMentionMessages(
|
|
|
290
309
|
return mentionCandidatesPromise;
|
|
291
310
|
};
|
|
292
311
|
|
|
312
|
+
const shownInBatch = new Set<string>();
|
|
293
313
|
for (const filePath of filePaths) {
|
|
294
314
|
const resolvedPath = await resolveMentionPath(filePath, cwd, getMentionCandidates);
|
|
295
315
|
if (!resolvedPath) {
|
|
296
316
|
continue;
|
|
297
317
|
}
|
|
298
318
|
const absolutePath = resolveReadPath(resolvedPath, cwd);
|
|
319
|
+
// Duplicate suppression, keyed by absolute path: same path shown earlier this
|
|
320
|
+
// batch or recently in the conversation → compact note, no re-read (the path
|
|
321
|
+
// stays referenced so the model can re-fetch via the read tool).
|
|
322
|
+
if (shownInBatch.has(absolutePath) || options?.recentlyShownPaths?.has(absolutePath)) {
|
|
323
|
+
files.push({
|
|
324
|
+
path: resolvedPath,
|
|
325
|
+
content: `(already shown in this conversation; not re-read — use the read tool to fetch \`${resolvedPath}\` again)`,
|
|
326
|
+
duplicate: true,
|
|
327
|
+
});
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
shownInBatch.add(absolutePath);
|
|
299
331
|
try {
|
|
300
332
|
const stat = await Bun.file(absolutePath).stat();
|
|
301
333
|
if (stat.isDirectory()) {
|
|
@@ -354,7 +386,7 @@ export async function generateFileMentionMessages(
|
|
|
354
386
|
}
|
|
355
387
|
|
|
356
388
|
const content = await Bun.file(absolutePath).text();
|
|
357
|
-
let { output, lineCount } = buildTextOutput(content);
|
|
389
|
+
let { output, lineCount } = buildTextOutput(content, options?.maxInlineBytes);
|
|
358
390
|
if (options?.useHashLines) {
|
|
359
391
|
output = formatHashLines(output);
|
|
360
392
|
}
|
package/src/utils/git.ts
CHANGED
|
@@ -1317,6 +1317,79 @@ export async function clean(
|
|
|
1317
1317
|
await runEffect(cwd, args, { signal: options.signal });
|
|
1318
1318
|
}
|
|
1319
1319
|
|
|
1320
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
1321
|
+
// API: bisect
|
|
1322
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
1323
|
+
|
|
1324
|
+
export interface BisectCommitInfo {
|
|
1325
|
+
sha: string;
|
|
1326
|
+
author: string;
|
|
1327
|
+
date: string;
|
|
1328
|
+
subject: string;
|
|
1329
|
+
stat: string;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Thin wrappers over `git bisect`. The marking commands (`good`/`bad`/`skip`)
|
|
1334
|
+
* return the raw {@link GitCommandResult} without throwing so callers can both
|
|
1335
|
+
* detect a genuine failure (non-zero exit) and scan the output for the
|
|
1336
|
+
* "<sha> is the first bad commit" conclusion. `reset` is best-effort and never
|
|
1337
|
+
* throws so it is safe to call from a `finally` block to restore the worktree.
|
|
1338
|
+
*/
|
|
1339
|
+
export const bisect = {
|
|
1340
|
+
/** Begin a bisect session (does not seed good/bad endpoints). */
|
|
1341
|
+
async start(cwd: string, signal?: AbortSignal): Promise<void> {
|
|
1342
|
+
await runEffect(cwd, ["bisect", "start"], { signal });
|
|
1343
|
+
},
|
|
1344
|
+
|
|
1345
|
+
/** Mark a revision (or current HEAD when omitted) good. Returns raw output. */
|
|
1346
|
+
async good(cwd: string, ref?: string, signal?: AbortSignal): Promise<GitCommandResult> {
|
|
1347
|
+
return runCommand(cwd, ref ? ["bisect", "good", ref] : ["bisect", "good"], { signal });
|
|
1348
|
+
},
|
|
1349
|
+
|
|
1350
|
+
/** Mark a revision (or current HEAD when omitted) bad. Returns raw output. */
|
|
1351
|
+
async bad(cwd: string, ref?: string, signal?: AbortSignal): Promise<GitCommandResult> {
|
|
1352
|
+
return runCommand(cwd, ref ? ["bisect", "bad", ref] : ["bisect", "bad"], { signal });
|
|
1353
|
+
},
|
|
1354
|
+
|
|
1355
|
+
/** Skip the current (untestable) revision. Returns raw output. */
|
|
1356
|
+
async skip(cwd: string, signal?: AbortSignal): Promise<GitCommandResult> {
|
|
1357
|
+
return runCommand(cwd, ["bisect", "skip"], { signal });
|
|
1358
|
+
},
|
|
1359
|
+
|
|
1360
|
+
/** Read the replayable bisect log for the current session. */
|
|
1361
|
+
async log(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
1362
|
+
return runText(cwd, ["bisect", "log"], { signal });
|
|
1363
|
+
},
|
|
1364
|
+
|
|
1365
|
+
/** End the bisect session, restoring the original HEAD. Never throws. */
|
|
1366
|
+
async reset(cwd: string, signal?: AbortSignal): Promise<void> {
|
|
1367
|
+
await runCommand(cwd, ["bisect", "reset"], { signal });
|
|
1368
|
+
},
|
|
1369
|
+
|
|
1370
|
+
/** True when `ancestor` is a first-parent-or-merge ancestor of `descendant`. */
|
|
1371
|
+
async isAncestor(cwd: string, ancestor: string, descendant: string, signal?: AbortSignal): Promise<boolean> {
|
|
1372
|
+
const result = await runCommand(cwd, ["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
1373
|
+
readOnly: true,
|
|
1374
|
+
signal,
|
|
1375
|
+
});
|
|
1376
|
+
return result.exitCode === 0;
|
|
1377
|
+
},
|
|
1378
|
+
|
|
1379
|
+
/** Resolve author/date/subject and a diffstat for a commit (for the receipt). */
|
|
1380
|
+
async describe(cwd: string, sha: string, signal?: AbortSignal): Promise<BisectCommitInfo> {
|
|
1381
|
+
// Newline-delimited so the format string carries no NUL byte (argv cannot
|
|
1382
|
+
// contain NUL); %s is already a single line, so the four fields never wrap.
|
|
1383
|
+
const meta = await runText(cwd, ["show", "-s", "--format=%H%n%an <%ae>%n%aI%n%s", sha], {
|
|
1384
|
+
readOnly: true,
|
|
1385
|
+
signal,
|
|
1386
|
+
});
|
|
1387
|
+
const [resolvedSha = sha, author = "", date = "", subject = ""] = meta.split("\n");
|
|
1388
|
+
const stat = await runText(cwd, ["show", "--stat", "--format=", sha], { readOnly: true, signal });
|
|
1389
|
+
return { sha: resolvedSha, author, date, subject, stat: stat.trim() };
|
|
1390
|
+
},
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1320
1393
|
// ════════════════════════════════════════════════════════════════════════════
|
|
1321
1394
|
// API: ls
|
|
1322
1395
|
// ════════════════════════════════════════════════════════════════════════════
|
|
@@ -10,44 +10,56 @@ import { extractWithParallel, findParallelApiKey, getParallelExtractContent } fr
|
|
|
10
10
|
import type { RenderResult, SpecialHandler } from "./types";
|
|
11
11
|
import { buildResult, formatMediaDuration, formatNumber } from "./types";
|
|
12
12
|
|
|
13
|
-
interface YouTubeUrl {
|
|
13
|
+
export interface YouTubeUrl {
|
|
14
14
|
videoId: string;
|
|
15
15
|
playlistId?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const YOUTUBE_VIDEO_ID_PATTERN = /^[a-zA-Z0-9_-]{11}$/;
|
|
19
|
+
|
|
20
|
+
function isYouTubeHost(hostname: string): boolean {
|
|
21
|
+
return hostname === "youtube.com" || hostname === "m.youtube.com" || hostname === "music.youtube.com";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isYouTubeEmbedHost(hostname: string): boolean {
|
|
25
|
+
return isYouTubeHost(hostname) || hostname === "youtube-nocookie.com";
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Parse YouTube URL into components
|
|
20
30
|
*/
|
|
21
|
-
function parseYouTubeUrl(url: string): YouTubeUrl | null {
|
|
31
|
+
export function parseYouTubeUrl(url: string): YouTubeUrl | null {
|
|
22
32
|
try {
|
|
23
33
|
const parsed = new URL(url);
|
|
24
34
|
const hostname = parsed.hostname.replace(/^www\./, "");
|
|
25
35
|
|
|
26
|
-
// youtube.com/watch?v=VIDEO_ID
|
|
27
|
-
if ((hostname
|
|
36
|
+
// youtube.com/watch?v=VIDEO_ID and music.youtube.com/watch?v=VIDEO_ID
|
|
37
|
+
if (isYouTubeHost(hostname) && parsed.pathname === "/watch") {
|
|
28
38
|
const videoId = parsed.searchParams.get("v");
|
|
29
39
|
const playlistId = parsed.searchParams.get("list") || undefined;
|
|
30
|
-
if (videoId) return { videoId, playlistId };
|
|
40
|
+
if (videoId && YOUTUBE_VIDEO_ID_PATTERN.test(videoId)) return { videoId, playlistId };
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
// youtube.com/v/VIDEO_ID
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
// youtube.com/v/VIDEO_ID, youtube.com/embed/VIDEO_ID, youtube.com/live/VIDEO_ID,
|
|
44
|
+
// and youtube-nocookie.com/embed/VIDEO_ID. Require the id segment to end
|
|
45
|
+
// before matching so malformed overlong ids are not silently truncated.
|
|
46
|
+
if (isYouTubeEmbedHost(hostname)) {
|
|
47
|
+
const match = parsed.pathname.match(/^\/(v|embed|live)\/([a-zA-Z0-9_-]{11})(?:$|\/)/);
|
|
36
48
|
if (match) return { videoId: match[2] };
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
// youtu.be/VIDEO_ID
|
|
40
52
|
if (hostname === "youtu.be") {
|
|
41
53
|
const videoId = parsed.pathname.slice(1).split("/")[0];
|
|
42
|
-
if (videoId &&
|
|
54
|
+
if (videoId && YOUTUBE_VIDEO_ID_PATTERN.test(videoId)) {
|
|
43
55
|
return { videoId };
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
// youtube.com/shorts/VIDEO_ID
|
|
48
|
-
if (hostname
|
|
59
|
+
// youtube.com/shorts/VIDEO_ID and m.youtube.com/shorts/VIDEO_ID
|
|
60
|
+
if (isYouTubeHost(hostname) && parsed.pathname.startsWith("/shorts/")) {
|
|
49
61
|
const videoId = parsed.pathname.replace("/shorts/", "").split("/")[0];
|
|
50
|
-
if (videoId &&
|
|
62
|
+
if (videoId && YOUTUBE_VIDEO_ID_PATTERN.test(videoId)) {
|
|
51
63
|
return { videoId };
|
|
52
64
|
}
|
|
53
65
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildWorkflowIntentReport,
|
|
3
|
+
type WorkflowClaimsLedger,
|
|
4
|
+
type WorkflowConsensusReport,
|
|
5
|
+
} from "./workflow-intent-report";
|
|
6
|
+
|
|
1
7
|
export const WORKFLOW_INTENT_DIFF_CUSTOM_TYPE = "workflow-intent-diff";
|
|
8
|
+
export const WORKFLOW_INTENT_ROUTES = ["direct", "deep-interview", "ralplan", "ultragoal", "team"] as const;
|
|
9
|
+
export const WORKFLOW_ESCALATION_ROUTES = ["deep-interview", "ralplan", "ultragoal", "team"] as const;
|
|
2
10
|
|
|
3
|
-
export type WorkflowIntentRoute =
|
|
11
|
+
export type WorkflowIntentRoute = (typeof WORKFLOW_INTENT_ROUTES)[number];
|
|
12
|
+
export type WorkflowEscalationRoute = (typeof WORKFLOW_ESCALATION_ROUTES)[number];
|
|
4
13
|
export type DirectTrackingMode = "custom-entry-only" | "not-direct";
|
|
5
14
|
export type RootCausePhaseStatus = "active" | "inactive";
|
|
6
15
|
|
|
@@ -9,13 +18,15 @@ export interface WorkflowIntentDiff {
|
|
|
9
18
|
readonly route: WorkflowIntentRoute;
|
|
10
19
|
readonly reason: string;
|
|
11
20
|
readonly directTracking: DirectTrackingMode;
|
|
12
|
-
readonly recommendedSkill?:
|
|
21
|
+
readonly recommendedSkill?: WorkflowEscalationRoute;
|
|
13
22
|
readonly recommendedInvocation?: string;
|
|
14
23
|
readonly triggers: readonly string[];
|
|
15
24
|
readonly rootCausePhase: {
|
|
16
25
|
readonly status: RootCausePhaseStatus;
|
|
17
26
|
readonly triggers: readonly string[];
|
|
18
27
|
};
|
|
28
|
+
readonly claimsLedger: WorkflowClaimsLedger;
|
|
29
|
+
readonly consensusReport: WorkflowConsensusReport;
|
|
19
30
|
readonly promptPreview: string;
|
|
20
31
|
}
|
|
21
32
|
|
|
@@ -182,6 +193,19 @@ export function buildWorkflowIntentDiff(text: string): WorkflowIntentDiff | null
|
|
|
182
193
|
const rootCauseTriggers = collectRootCauseTriggers(text);
|
|
183
194
|
const rootCauseActive = rootCauseTriggers.length > 0;
|
|
184
195
|
const direct = route.route === "direct";
|
|
196
|
+
const rootCauseStatus: RootCausePhaseStatus = rootCauseActive ? "active" : "inactive";
|
|
197
|
+
const rootCausePhase = {
|
|
198
|
+
status: rootCauseStatus,
|
|
199
|
+
triggers: rootCauseTriggers,
|
|
200
|
+
};
|
|
201
|
+
const report = buildWorkflowIntentReport({
|
|
202
|
+
route: route.route,
|
|
203
|
+
reason: route.reason,
|
|
204
|
+
direct,
|
|
205
|
+
recommendedInvocation: route.recommendedInvocation,
|
|
206
|
+
triggers: route.triggers,
|
|
207
|
+
rootCausePhase,
|
|
208
|
+
});
|
|
185
209
|
|
|
186
210
|
return {
|
|
187
211
|
version: 1,
|
|
@@ -190,10 +214,9 @@ export function buildWorkflowIntentDiff(text: string): WorkflowIntentDiff | null
|
|
|
190
214
|
directTracking: direct ? "custom-entry-only" : "not-direct",
|
|
191
215
|
...(direct ? {} : { recommendedSkill: route.route, recommendedInvocation: route.recommendedInvocation }),
|
|
192
216
|
triggers: route.triggers,
|
|
193
|
-
rootCausePhase
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
217
|
+
rootCausePhase,
|
|
218
|
+
claimsLedger: report.claimsLedger,
|
|
219
|
+
consensusReport: report.consensusReport,
|
|
197
220
|
promptPreview,
|
|
198
221
|
};
|
|
199
222
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { SessionEntry } from "../session/session-manager";
|
|
2
|
+
import {
|
|
3
|
+
WORKFLOW_INTENT_DIFF_CUSTOM_TYPE,
|
|
4
|
+
WORKFLOW_INTENT_ROUTES,
|
|
5
|
+
type WorkflowIntentDiff,
|
|
6
|
+
type WorkflowIntentRoute,
|
|
7
|
+
} from "./workflow-intent-diff";
|
|
8
|
+
import type { WorkflowSignalObserver } from "./workflow-intent-report";
|
|
9
|
+
import { parseWorkflowIntentDiffPayload } from "./workflow-intent-report-schema";
|
|
10
|
+
|
|
11
|
+
export type WorkflowConsumerConfidence = "none" | "low" | "medium" | "high";
|
|
12
|
+
|
|
13
|
+
export interface WorkflowRouteCounts {
|
|
14
|
+
readonly direct: number;
|
|
15
|
+
readonly "deep-interview": number;
|
|
16
|
+
readonly ralplan: number;
|
|
17
|
+
readonly ultragoal: number;
|
|
18
|
+
readonly team: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WorkflowIntentReportEntry {
|
|
22
|
+
readonly entryId: string;
|
|
23
|
+
readonly timestamp: string;
|
|
24
|
+
readonly intent: WorkflowIntentDiff;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WorkflowIntentReportTotals {
|
|
28
|
+
readonly total: number;
|
|
29
|
+
readonly byRoute: WorkflowRouteCounts;
|
|
30
|
+
readonly escalationRequired: number;
|
|
31
|
+
readonly rootCauseActive: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WorkflowIntentReportCollection {
|
|
35
|
+
readonly entries: readonly WorkflowIntentReportEntry[];
|
|
36
|
+
readonly latest: WorkflowIntentReportEntry | undefined;
|
|
37
|
+
readonly totals: WorkflowIntentReportTotals;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WorkflowObserverSignalSummary {
|
|
41
|
+
readonly observer: WorkflowSignalObserver;
|
|
42
|
+
readonly conclusion: string;
|
|
43
|
+
readonly count: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkflowConsensusSummary {
|
|
47
|
+
readonly total: number;
|
|
48
|
+
readonly dominantRoute: WorkflowIntentRoute | undefined;
|
|
49
|
+
readonly confidence: WorkflowConsumerConfidence;
|
|
50
|
+
readonly escalationGate: {
|
|
51
|
+
readonly required: boolean;
|
|
52
|
+
readonly count: number;
|
|
53
|
+
readonly latestReason: string | undefined;
|
|
54
|
+
};
|
|
55
|
+
readonly rootCausePhase: {
|
|
56
|
+
readonly active: boolean;
|
|
57
|
+
readonly count: number;
|
|
58
|
+
readonly triggers: readonly string[];
|
|
59
|
+
};
|
|
60
|
+
readonly observerSignals: readonly WorkflowObserverSignalSummary[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emptyRouteCounts(): WorkflowRouteCounts {
|
|
64
|
+
return {
|
|
65
|
+
direct: 0,
|
|
66
|
+
"deep-interview": 0,
|
|
67
|
+
ralplan: 0,
|
|
68
|
+
ultragoal: 0,
|
|
69
|
+
team: 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseWorkflowIntentDiffEntry(entry: SessionEntry): WorkflowIntentReportEntry | undefined {
|
|
74
|
+
if (entry.type !== "custom" || entry.customType !== WORKFLOW_INTENT_DIFF_CUSTOM_TYPE) return undefined;
|
|
75
|
+
const intent = parseWorkflowIntentDiffPayload(entry.data);
|
|
76
|
+
if (!intent) return undefined;
|
|
77
|
+
return { entryId: entry.id, timestamp: entry.timestamp, intent };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function incrementRoute(counts: WorkflowRouteCounts, route: WorkflowIntentRoute): WorkflowRouteCounts {
|
|
81
|
+
switch (route) {
|
|
82
|
+
case "direct":
|
|
83
|
+
return { ...counts, direct: counts.direct + 1 };
|
|
84
|
+
case "deep-interview":
|
|
85
|
+
return { ...counts, "deep-interview": counts["deep-interview"] + 1 };
|
|
86
|
+
case "ralplan":
|
|
87
|
+
return { ...counts, ralplan: counts.ralplan + 1 };
|
|
88
|
+
case "ultragoal":
|
|
89
|
+
return { ...counts, ultragoal: counts.ultragoal + 1 };
|
|
90
|
+
case "team":
|
|
91
|
+
return { ...counts, team: counts.team + 1 };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function routeCount(counts: WorkflowRouteCounts, route: WorkflowIntentRoute): number {
|
|
96
|
+
switch (route) {
|
|
97
|
+
case "direct":
|
|
98
|
+
return counts.direct;
|
|
99
|
+
case "deep-interview":
|
|
100
|
+
return counts["deep-interview"];
|
|
101
|
+
case "ralplan":
|
|
102
|
+
return counts.ralplan;
|
|
103
|
+
case "ultragoal":
|
|
104
|
+
return counts.ultragoal;
|
|
105
|
+
case "team":
|
|
106
|
+
return counts.team;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function collectWorkflowIntentReports(entries: readonly SessionEntry[]): WorkflowIntentReportCollection {
|
|
111
|
+
const reports: WorkflowIntentReportEntry[] = [];
|
|
112
|
+
let byRoute = emptyRouteCounts();
|
|
113
|
+
let escalationRequired = 0;
|
|
114
|
+
let rootCauseActive = 0;
|
|
115
|
+
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const report = parseWorkflowIntentDiffEntry(entry);
|
|
118
|
+
if (!report) continue;
|
|
119
|
+
reports.push(report);
|
|
120
|
+
byRoute = incrementRoute(byRoute, report.intent.route);
|
|
121
|
+
if (report.intent.consensusReport.escalationGate.status === "required") escalationRequired += 1;
|
|
122
|
+
if (report.intent.rootCausePhase.status === "active") rootCauseActive += 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
entries: reports,
|
|
127
|
+
latest: reports[reports.length - 1],
|
|
128
|
+
totals: {
|
|
129
|
+
total: reports.length,
|
|
130
|
+
byRoute,
|
|
131
|
+
escalationRequired,
|
|
132
|
+
rootCauseActive,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function dominantRouteFor(counts: WorkflowRouteCounts, total: number): WorkflowIntentRoute | undefined {
|
|
138
|
+
if (total === 0) return undefined;
|
|
139
|
+
let dominant: WorkflowIntentRoute | undefined;
|
|
140
|
+
let dominantCount = 0;
|
|
141
|
+
let tied = false;
|
|
142
|
+
|
|
143
|
+
for (const route of WORKFLOW_INTENT_ROUTES) {
|
|
144
|
+
const count = routeCount(counts, route);
|
|
145
|
+
if (count > dominantCount) {
|
|
146
|
+
dominant = route;
|
|
147
|
+
dominantCount = count;
|
|
148
|
+
tied = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (count === dominantCount && count > 0) tied = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return tied ? undefined : dominant;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function confidenceFor(
|
|
158
|
+
total: number,
|
|
159
|
+
dominantCount: number,
|
|
160
|
+
dominantRoute: WorkflowIntentRoute | undefined,
|
|
161
|
+
): WorkflowConsumerConfidence {
|
|
162
|
+
if (total === 0) return "none";
|
|
163
|
+
if (!dominantRoute) return "low";
|
|
164
|
+
const ratio = dominantCount / total;
|
|
165
|
+
if (ratio === 1) return "high";
|
|
166
|
+
if (ratio >= 0.5) return "medium";
|
|
167
|
+
return "low";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function summarizeWorkflowConsensus(intents: readonly WorkflowIntentDiff[]): WorkflowConsensusSummary {
|
|
171
|
+
let byRoute = emptyRouteCounts();
|
|
172
|
+
let escalationCount = 0;
|
|
173
|
+
let latestEscalationReason: string | undefined;
|
|
174
|
+
let activeRootCauseCount = 0;
|
|
175
|
+
const rootCauseTriggers: string[] = [];
|
|
176
|
+
const observerSignalCounts = new Map<string, WorkflowObserverSignalSummary>();
|
|
177
|
+
|
|
178
|
+
for (const intent of intents) {
|
|
179
|
+
byRoute = incrementRoute(byRoute, intent.route);
|
|
180
|
+
if (intent.consensusReport.escalationGate.status === "required") {
|
|
181
|
+
escalationCount += 1;
|
|
182
|
+
latestEscalationReason = intent.consensusReport.escalationGate.reason;
|
|
183
|
+
}
|
|
184
|
+
if (intent.rootCausePhase.status === "active") {
|
|
185
|
+
activeRootCauseCount += 1;
|
|
186
|
+
for (const trigger of intent.rootCausePhase.triggers) {
|
|
187
|
+
if (!rootCauseTriggers.includes(trigger)) rootCauseTriggers.push(trigger);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const signal of intent.consensusReport.observerSignals) {
|
|
191
|
+
const key = `${signal.observer}\u0000${signal.conclusion}`;
|
|
192
|
+
const existing = observerSignalCounts.get(key);
|
|
193
|
+
observerSignalCounts.set(key, {
|
|
194
|
+
observer: signal.observer,
|
|
195
|
+
conclusion: signal.conclusion,
|
|
196
|
+
count: existing ? existing.count + 1 : 1,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const total = intents.length;
|
|
202
|
+
const dominantRoute = dominantRouteFor(byRoute, total);
|
|
203
|
+
const dominantCount = dominantRoute ? routeCount(byRoute, dominantRoute) : 0;
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
total,
|
|
207
|
+
dominantRoute,
|
|
208
|
+
confidence: confidenceFor(total, dominantCount, dominantRoute),
|
|
209
|
+
escalationGate: {
|
|
210
|
+
required: escalationCount > 0,
|
|
211
|
+
count: escalationCount,
|
|
212
|
+
latestReason: latestEscalationReason,
|
|
213
|
+
},
|
|
214
|
+
rootCausePhase: {
|
|
215
|
+
active: activeRootCauseCount > 0,
|
|
216
|
+
count: activeRootCauseCount,
|
|
217
|
+
triggers: rootCauseTriggers,
|
|
218
|
+
},
|
|
219
|
+
observerSignals: Array.from(observerSignalCounts.values()),
|
|
220
|
+
};
|
|
221
|
+
}
|