@sireai/optimus 0.1.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/.env.example +16 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/cli/optimus.d.ts +2 -0
- package/dist/cli/optimus.js +2951 -0
- package/dist/cli/optimus.js.map +1 -0
- package/dist/cli/self-update.d.ts +49 -0
- package/dist/cli/self-update.js +264 -0
- package/dist/cli/self-update.js.map +1 -0
- package/dist/config/load-config.d.ts +3 -0
- package/dist/config/load-config.js +321 -0
- package/dist/config/load-config.js.map +1 -0
- package/dist/config/optimus-paths.d.ts +13 -0
- package/dist/config/optimus-paths.js +44 -0
- package/dist/config/optimus-paths.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/jira/jira-cli.d.ts +1 -0
- package/dist/integrations/jira/jira-cli.js +278 -0
- package/dist/integrations/jira/jira-cli.js.map +1 -0
- package/dist/integrations/jira/jira-client.d.ts +99 -0
- package/dist/integrations/jira/jira-client.js +521 -0
- package/dist/integrations/jira/jira-client.js.map +1 -0
- package/dist/integrations/jira/jira-submit.d.ts +71 -0
- package/dist/integrations/jira/jira-submit.js +351 -0
- package/dist/integrations/jira/jira-submit.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.d.ts +23 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js +136 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.d.ts +6 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js +81 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.d.ts +2 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js +49 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-global-config.d.ts +17 -0
- package/dist/problem-solving-core/codex/codex-global-config.js +100 -0
- package/dist/problem-solving-core/codex/codex-global-config.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-preflight.d.ts +13 -0
- package/dist/problem-solving-core/codex/codex-preflight.js +142 -0
- package/dist/problem-solving-core/codex/codex-preflight.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.d.ts +14 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js +68 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-required-env.d.ts +3 -0
- package/dist/problem-solving-core/codex/codex-required-env.js +21 -0
- package/dist/problem-solving-core/codex/codex-required-env.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-runner.d.ts +37 -0
- package/dist/problem-solving-core/codex/codex-runner.js +926 -0
- package/dist/problem-solving-core/codex/codex-runner.js.map +1 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.d.ts +36 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js +143 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js.map +1 -0
- package/dist/problem-solving-core/codex/repo-memory-service.d.ts +24 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js +114 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js.map +1 -0
- package/dist/problem-solving-core/codex/skill-sync-service.d.ts +35 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js +280 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-abort-registry.d.ts +17 -0
- package/dist/task-environment/cancellation/task-abort-registry.js +51 -0
- package/dist/task-environment/cancellation/task-abort-registry.js.map +1 -0
- package/dist/task-environment/cancellation/task-cancellation-service.d.ts +25 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js +54 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-cleanup-service.d.ts +22 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js +67 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +13 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +83 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.d.ts +6 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js +15 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.d.ts +16 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js +2 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js.map +1 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +50 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +454 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -0
- package/dist/task-environment/delivery/feishu-card-renderer.d.ts +38 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js +449 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.d.ts +34 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js +201 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.d.ts +27 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +74 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -0
- package/dist/task-environment/delivery/feishu-notifier.d.ts +45 -0
- package/dist/task-environment/delivery/feishu-notifier.js +250 -0
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +39 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js +33 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.d.ts +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js +11 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.d.ts +20 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.d.ts +14 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js +109 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-service.d.ts +33 -0
- package/dist/task-environment/delivery/task-delivery-service.js +432 -0
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -0
- package/dist/task-environment/delivery/task-publication-service.d.ts +97 -0
- package/dist/task-environment/delivery/task-publication-service.js +1369 -0
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -0
- package/dist/task-environment/execution-addresses.d.ts +40 -0
- package/dist/task-environment/execution-addresses.js +63 -0
- package/dist/task-environment/execution-addresses.js.map +1 -0
- package/dist/task-environment/intake/cli-file-intake.d.ts +12 -0
- package/dist/task-environment/intake/cli-file-intake.js +56 -0
- package/dist/task-environment/intake/cli-file-intake.js.map +1 -0
- package/dist/task-environment/intake/manual-problem-intake.d.ts +3 -0
- package/dist/task-environment/intake/manual-problem-intake.js +57 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -0
- package/dist/task-environment/intake/polling-problem-intake.d.ts +14 -0
- package/dist/task-environment/intake/polling-problem-intake.js +232 -0
- package/dist/task-environment/intake/polling-problem-intake.js.map +1 -0
- package/dist/task-environment/observability/logger.d.ts +76 -0
- package/dist/task-environment/observability/logger.js +604 -0
- package/dist/task-environment/observability/logger.js.map +1 -0
- package/dist/task-environment/observability/runtime-panel.d.ts +82 -0
- package/dist/task-environment/observability/runtime-panel.js +1008 -0
- package/dist/task-environment/observability/runtime-panel.js.map +1 -0
- package/dist/task-environment/observability/sound-notifier.d.ts +18 -0
- package/dist/task-environment/observability/sound-notifier.js +71 -0
- package/dist/task-environment/observability/sound-notifier.js.map +1 -0
- package/dist/task-environment/orchestration/execution-context-assembler.d.ts +41 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js +464 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/git-change-classifier.d.ts +19 -0
- package/dist/task-environment/orchestration/git-change-classifier.js +106 -0
- package/dist/task-environment/orchestration/git-change-classifier.js.map +1 -0
- package/dist/task-environment/orchestration/harness-registry.d.ts +27 -0
- package/dist/task-environment/orchestration/harness-registry.js +116 -0
- package/dist/task-environment/orchestration/harness-registry.js.map +1 -0
- package/dist/task-environment/orchestration/harness-resolver.d.ts +8 -0
- package/dist/task-environment/orchestration/harness-resolver.js +39 -0
- package/dist/task-environment/orchestration/harness-resolver.js.map +1 -0
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +45 -0
- package/dist/task-environment/orchestration/task-orchestrator.js +1122 -0
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -0
- package/dist/task-environment/orchestration/task-package-assembler.d.ts +4 -0
- package/dist/task-environment/orchestration/task-package-assembler.js +10 -0
- package/dist/task-environment/orchestration/task-package-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/triage-agent.d.ts +54 -0
- package/dist/task-environment/orchestration/triage-agent.js +636 -0
- package/dist/task-environment/orchestration/triage-agent.js.map +1 -0
- package/dist/task-environment/orchestration/triage-runner.d.ts +65 -0
- package/dist/task-environment/orchestration/triage-runner.js +655 -0
- package/dist/task-environment/orchestration/triage-runner.js.map +1 -0
- package/dist/task-environment/publication-target.d.ts +12 -0
- package/dist/task-environment/publication-target.js +174 -0
- package/dist/task-environment/publication-target.js.map +1 -0
- package/dist/task-environment/runtime/blocking-event-queue.d.ts +7 -0
- package/dist/task-environment/runtime/blocking-event-queue.js +27 -0
- package/dist/task-environment/runtime/blocking-event-queue.js.map +1 -0
- package/dist/task-environment/runtime/optimus-runtime.d.ts +69 -0
- package/dist/task-environment/runtime/optimus-runtime.js +751 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -0
- package/dist/task-environment/storage/sqlite-event-store.d.ts +52 -0
- package/dist/task-environment/storage/sqlite-event-store.js +288 -0
- package/dist/task-environment/storage/sqlite-event-store.js.map +1 -0
- package/dist/task-environment/storage/sqlite-task-store.d.ts +122 -0
- package/dist/task-environment/storage/sqlite-task-store.js +1182 -0
- package/dist/task-environment/storage/sqlite-task-store.js.map +1 -0
- package/dist/types.d.ts +629 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/embedded-skills/shared/repo-inspection/SKILL.md +9 -0
- package/embedded-skills/shared/repo-inspection/skill.json +5 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +10 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +6 -0
- package/harness/AGENTS.md +30 -0
- package/harness/CHECKLIST.md +44 -0
- package/harness/CONSTRAINTS.md +60 -0
- package/harness/FRAMEWORK.md +28 -0
- package/harness/GOAL.md +28 -0
- package/harness/HANDOFF.md +45 -0
- package/harness/TASK_PLAN.md +79 -0
- package/optimus.config.template.json +34 -0
- package/package.json +109 -0
- package/task-harnesses/bugfix/ACCEPT.md +47 -0
- package/task-harnesses/bugfix/CONSTRAINTS.md +46 -0
- package/task-harnesses/bugfix/CONTEXT.md +29 -0
- package/task-harnesses/bugfix/EVOLUTION.md +82 -0
- package/task-harnesses/bugfix/ROLE.md +29 -0
- package/task-harnesses/bugfix/STANDARD.md +250 -0
- package/task-harnesses/bugfix/manifest.json +13 -0
- package/task-harnesses/registry.json +8 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Codex } from "@openai/codex-sdk";
|
|
6
|
+
import { CodexAuthResolver } from "../../problem-solving-core/codex/codex-auth-resolver.js";
|
|
7
|
+
import { classifyCodexFailureCategory } from "../../problem-solving-core/codex/codex-failure-classifier.js";
|
|
8
|
+
import { CodexPreflight } from "../../problem-solving-core/codex/codex-preflight.js";
|
|
9
|
+
import { renderManagedCodexHomeConfig, resolveCodexSdkConfigOverrides, resolveEffectiveCodexModel, useManagedCodexHome } from "../../problem-solving-core/codex/codex-provider-profile.js";
|
|
10
|
+
import { OptimusLogger } from "../observability/logger.js";
|
|
11
|
+
import { HarnessRegistry } from "./harness-registry.js";
|
|
12
|
+
const SUPPORTED_EVENT_TYPES = new Set([
|
|
13
|
+
"task.submitted_manually",
|
|
14
|
+
"problem.discovered",
|
|
15
|
+
"bug.discovered",
|
|
16
|
+
"task.retry_requested",
|
|
17
|
+
"task.cancel_requested"
|
|
18
|
+
]);
|
|
19
|
+
class TriageAgentError extends Error {
|
|
20
|
+
failureCategory;
|
|
21
|
+
constructor(message, failureCategory) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "TriageAgentError";
|
|
24
|
+
this.failureCategory = failureCategory;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// TriageAgent is the dedicated role that decides whether a raw event can become a governed task package.
|
|
28
|
+
// Runtime owns queueing and persistence, while this component owns semantic admission, task typing, and rejection reasons.
|
|
29
|
+
export class TriageAgent {
|
|
30
|
+
config;
|
|
31
|
+
harnessRegistry;
|
|
32
|
+
logger;
|
|
33
|
+
dependencies;
|
|
34
|
+
initialized = false;
|
|
35
|
+
constructor(config, dependencies = {}) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.harnessRegistry = new HarnessRegistry(config);
|
|
38
|
+
this.logger = new OptimusLogger(config);
|
|
39
|
+
this.dependencies = dependencies;
|
|
40
|
+
}
|
|
41
|
+
// initialize() is called during runtime startup so triage readiness becomes part of the resident service lifecycle.
|
|
42
|
+
// In codex_sdk mode it performs preflight plus a real warmup turn; startup must fail if this step fails.
|
|
43
|
+
async initialize() {
|
|
44
|
+
if (this.initialized) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (this.config.codex.provider !== "codex_sdk") {
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
await this.logger.info("triage.agent.ready", {
|
|
50
|
+
provider: this.config.codex.provider,
|
|
51
|
+
mode: "local_matcher"
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await this.logger.info("triage.agent.starting", {
|
|
56
|
+
provider: this.config.codex.provider,
|
|
57
|
+
model: resolveEffectiveCodexModel(this.config),
|
|
58
|
+
sandboxMode: this.config.codex.analysisSandbox,
|
|
59
|
+
approvalPolicy: this.config.codex.approvalPolicy
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
const preflight = await new CodexPreflight(this.config).run();
|
|
63
|
+
if (!preflight.ok) {
|
|
64
|
+
throw new TriageAgentError(preflight.summary, preflight.failureCategory ?? "auth_missing");
|
|
65
|
+
}
|
|
66
|
+
const warmupExecutor = this.dependencies.runCodexTurn ?? ((input) => this.runCodexTurn(input));
|
|
67
|
+
const warmup = await warmupExecutor({
|
|
68
|
+
prompt: [{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: [
|
|
71
|
+
"# Optimus Triage Warmup",
|
|
72
|
+
"Return JSON only.",
|
|
73
|
+
"Confirm that the triage agent is ready to classify runtime events.",
|
|
74
|
+
'{"ready": true, "summary": "triage_ready"}'
|
|
75
|
+
].join("\n")
|
|
76
|
+
}],
|
|
77
|
+
outputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
required: ["ready", "summary"],
|
|
81
|
+
properties: {
|
|
82
|
+
ready: { type: "boolean" },
|
|
83
|
+
summary: { type: "string" }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
timeoutMs: this.config.codex.execTimeoutMs,
|
|
87
|
+
sandboxMode: this.config.codex.analysisSandbox
|
|
88
|
+
});
|
|
89
|
+
const parsedWarmup = this.parseWarmupResponse(warmup.response);
|
|
90
|
+
if (!parsedWarmup.ready) {
|
|
91
|
+
throw new TriageAgentError(`Triage warmup returned ready=false: ${parsedWarmup.summary}`, "schema_error");
|
|
92
|
+
}
|
|
93
|
+
this.initialized = true;
|
|
94
|
+
await this.logger.info("triage.agent.ready", {
|
|
95
|
+
provider: this.config.codex.provider,
|
|
96
|
+
summary: parsedWarmup.summary,
|
|
97
|
+
...(warmup.sdkThreadId ? { sdkThreadId: warmup.sdkThreadId } : {})
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const failureCategory = this.resolveFailureCategory(error);
|
|
102
|
+
await this.logger.error("triage.agent.start_failed", {
|
|
103
|
+
provider: this.config.codex.provider,
|
|
104
|
+
failureCategory,
|
|
105
|
+
reason: error instanceof Error ? error.message : "Unknown triage startup failure"
|
|
106
|
+
});
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// decide() is the stable triage seam for the whole runtime.
|
|
111
|
+
// In codex_sdk mode it uses Codex to understand the event plus role contracts; in mock mode it uses the deterministic local matcher.
|
|
112
|
+
async decide(event) {
|
|
113
|
+
const content = event.content.content.trim();
|
|
114
|
+
const title = event.content.title?.trim() || this.deriveTitle(content);
|
|
115
|
+
if (!content) {
|
|
116
|
+
return {
|
|
117
|
+
decision: "rejected",
|
|
118
|
+
summary: "Runtime event is missing usable content for triage.",
|
|
119
|
+
reason: "input_missing",
|
|
120
|
+
missingInfo: ["content"]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!title) {
|
|
124
|
+
return {
|
|
125
|
+
decision: "rejected",
|
|
126
|
+
summary: "Runtime event is missing a stable title for triage.",
|
|
127
|
+
reason: "title_missing",
|
|
128
|
+
missingInfo: ["title"]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Control events are runtime governance concerns and must not re-enter semantic task admission.
|
|
132
|
+
if (event.type === "task.retry_requested" || event.type === "task.cancel_requested") {
|
|
133
|
+
return {
|
|
134
|
+
decision: "rejected",
|
|
135
|
+
summary: `Event ${event.type} is handled directly by runtime without triage packaging.`,
|
|
136
|
+
reason: "runtime_control_event"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const contracts = this.loadRoleContracts();
|
|
140
|
+
if (this.config.codex.provider !== "codex_sdk") {
|
|
141
|
+
return this.decideLocally(event, title, content, contracts);
|
|
142
|
+
}
|
|
143
|
+
await this.initialize();
|
|
144
|
+
const codexDecision = await this.decideWithCodex(event, title, content, contracts);
|
|
145
|
+
return this.materializeDecision(event, title, content, contracts, codexDecision);
|
|
146
|
+
}
|
|
147
|
+
// Triage contracts are discovered from the harness manifest so admission rules are explicit and decoupled from execution documents.
|
|
148
|
+
loadRoleContracts() {
|
|
149
|
+
return this.harnessRegistry.list().map((definition) => this.readRoleContract(definition));
|
|
150
|
+
}
|
|
151
|
+
readRoleContract(definition) {
|
|
152
|
+
const manifest = this.harnessRegistry.getManifest(definition.taskType);
|
|
153
|
+
const rulePaths = manifest.triageRules.map((file) => join(manifest.harnessDir, file));
|
|
154
|
+
const triageRulesMarkdown = rulePaths
|
|
155
|
+
.map((path) => [
|
|
156
|
+
`<!-- ${path} -->`,
|
|
157
|
+
readFileSync(path, "utf8")
|
|
158
|
+
].join("\n"))
|
|
159
|
+
.join("\n\n---\n\n");
|
|
160
|
+
const eventTypes = this.extractList(triageRulesMarkdown, "## 事件范围")
|
|
161
|
+
.map((eventType) => eventType.replace(/`/g, "").trim())
|
|
162
|
+
.filter(Boolean);
|
|
163
|
+
const missingInfoFields = this.extractList(triageRulesMarkdown, "## 拒绝时优先反馈的缺失信息")
|
|
164
|
+
.map((field) => field.replace(/`/g, "").trim())
|
|
165
|
+
.filter(Boolean);
|
|
166
|
+
if (eventTypes.length === 0) {
|
|
167
|
+
throw new Error(`Triage rules for taskType ${definition.taskType} must define a non-empty 事件范围 list.`);
|
|
168
|
+
}
|
|
169
|
+
const normalizedEventTypes = eventTypes.map((eventType) => {
|
|
170
|
+
if (!SUPPORTED_EVENT_TYPES.has(eventType)) {
|
|
171
|
+
throw new Error(`Triage rules for taskType ${definition.taskType} declare unsupported 事件范围 entry ${eventType}.`);
|
|
172
|
+
}
|
|
173
|
+
return eventType;
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
taskType: definition.taskType,
|
|
177
|
+
rulePaths,
|
|
178
|
+
triageRulesMarkdown,
|
|
179
|
+
eventTypes: normalizedEventTypes,
|
|
180
|
+
missingInfoFields
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// The Codex-backed path lets a real agent interpret ambiguous natural language using the same harness family that the executor will later consume.
|
|
184
|
+
async decideWithCodex(event, title, content, contracts) {
|
|
185
|
+
const prompt = this.buildCodexPrompt(event, title, content, contracts);
|
|
186
|
+
const turnExecutor = this.dependencies.runCodexTurn ?? ((input) => this.runCodexTurn(input));
|
|
187
|
+
const result = await turnExecutor({
|
|
188
|
+
prompt,
|
|
189
|
+
outputSchema: this.buildOutputSchema(),
|
|
190
|
+
timeoutMs: this.config.codex.execTimeoutMs,
|
|
191
|
+
sandboxMode: this.config.codex.analysisSandbox
|
|
192
|
+
});
|
|
193
|
+
const rawResponsePath = await this.writeDebugResponseArtifact(event.eventId, "triage-raw-response.json", result.response);
|
|
194
|
+
await this.logger.debug("triage.agent.raw_response", {
|
|
195
|
+
eventId: event.eventId,
|
|
196
|
+
eventType: event.type,
|
|
197
|
+
...(result.sdkThreadId ? { sdkThreadId: result.sdkThreadId } : {}),
|
|
198
|
+
responsePreview: this.previewResponse(result.response, 3000),
|
|
199
|
+
response: result.response,
|
|
200
|
+
responseArtifactPath: rawResponsePath
|
|
201
|
+
});
|
|
202
|
+
const parsed = this.parseCodexDecision(result.response, contracts);
|
|
203
|
+
const parsedPath = await this.writeDebugResponseArtifact(event.eventId, "triage-parsed-response.json", JSON.stringify(parsed, null, 2));
|
|
204
|
+
await this.logger.debug("triage.agent.parsed_response", {
|
|
205
|
+
eventId: event.eventId,
|
|
206
|
+
eventType: event.type,
|
|
207
|
+
...(result.sdkThreadId ? { sdkThreadId: result.sdkThreadId } : {}),
|
|
208
|
+
decision: parsed.decision,
|
|
209
|
+
...(parsed.taskType ? { taskType: parsed.taskType } : {}),
|
|
210
|
+
parsedResponsePath: parsedPath
|
|
211
|
+
});
|
|
212
|
+
return parsed;
|
|
213
|
+
}
|
|
214
|
+
// Materialization keeps the model output narrow: Codex decides accept/reject and task type, while the system still assembles the final TaskPackage contract.
|
|
215
|
+
materializeDecision(event, title, content, contracts, decision) {
|
|
216
|
+
if (decision.decision === "rejected") {
|
|
217
|
+
return {
|
|
218
|
+
decision: "rejected",
|
|
219
|
+
summary: decision.summary,
|
|
220
|
+
reason: decision.reason ?? "unsupported_problem_type",
|
|
221
|
+
...(decision.missingInfo && decision.missingInfo.length > 0 ? { missingInfo: decision.missingInfo } : {})
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const matchedContract = contracts.find((contract) => contract.taskType === decision.taskType);
|
|
225
|
+
if (!matchedContract) {
|
|
226
|
+
throw new TriageAgentError(`Codex triage selected unsupported taskType ${decision.taskType ?? "<empty>"}.`, "schema_error");
|
|
227
|
+
}
|
|
228
|
+
const taskPackage = {
|
|
229
|
+
taskPackageId: `pkg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
230
|
+
sourceEventId: event.eventId,
|
|
231
|
+
source: event.content.source,
|
|
232
|
+
...(event.content.sourceRef ? { sourceRef: event.content.sourceRef } : {}),
|
|
233
|
+
idempotencyKey: this.buildIdempotencyKey(title),
|
|
234
|
+
taskType: matchedContract.taskType,
|
|
235
|
+
title,
|
|
236
|
+
objective: this.buildObjective(matchedContract.taskType),
|
|
237
|
+
input: {
|
|
238
|
+
title,
|
|
239
|
+
description: event.content.description ?? content,
|
|
240
|
+
content,
|
|
241
|
+
...(event.content.repo ? { repo: event.content.repo } : {}),
|
|
242
|
+
...(event.content.branch ? { branch: event.content.branch } : {}),
|
|
243
|
+
...(event.content.metadata ? { metadata: event.content.metadata } : {})
|
|
244
|
+
},
|
|
245
|
+
constraints: this.buildConstraints(event),
|
|
246
|
+
requestedCapabilities: this.buildRequestedCapabilities(matchedContract.taskType),
|
|
247
|
+
desiredArtifactKinds: this.resolveDesiredArtifactKinds(matchedContract.taskType),
|
|
248
|
+
triageSummary: decision.summary,
|
|
249
|
+
createdAt: new Date().toISOString()
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
decision: "accepted",
|
|
253
|
+
summary: taskPackage.triageSummary,
|
|
254
|
+
taskType: matchedContract.taskType,
|
|
255
|
+
taskPackage
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// The local matcher remains only for mock mode and deterministic tests.
|
|
259
|
+
// It uses a conservative bug-vs-non-bug heuristic while the real admission semantics stay in ACCEPT.md and Codex triage.
|
|
260
|
+
decideLocally(event, title, content, contracts) {
|
|
261
|
+
const hintedTaskType = typeof event.content.metadata?.taskTypeHint === "string" ? event.content.metadata.taskTypeHint.trim() : "";
|
|
262
|
+
const matchedContract = hintedTaskType
|
|
263
|
+
? (() => {
|
|
264
|
+
const hintedContract = contracts.find((contract) => contract.taskType === hintedTaskType);
|
|
265
|
+
if (!hintedContract) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
return this.matchesRoleContract(hintedContract, event, title, content) ? hintedContract : undefined;
|
|
269
|
+
})()
|
|
270
|
+
: contracts.find((contract) => this.matchesRoleContract(contract, event, title, content));
|
|
271
|
+
if (!matchedContract) {
|
|
272
|
+
return {
|
|
273
|
+
decision: "rejected",
|
|
274
|
+
summary: "Runtime event does not match any registered task harness role contract.",
|
|
275
|
+
reason: "unsupported_problem_type"
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return this.materializeDecision(event, title, content, contracts, {
|
|
279
|
+
decision: "accepted",
|
|
280
|
+
summary: `Triage accepted runtime event ${event.eventId} as ${matchedContract.taskType}.`,
|
|
281
|
+
taskType: matchedContract.taskType
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// The mock/local triage path is intentionally conservative.
|
|
285
|
+
// It only approximates bug-style requests for deterministic tests; the real routing contract is owned by ACCEPT.md.
|
|
286
|
+
matchesRoleContract(contract, event, title, content) {
|
|
287
|
+
if (!contract.eventTypes.includes(event.type)) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const description = typeof event.content.description === "string" ? event.content.description.trim() : "";
|
|
291
|
+
if (!title.trim() || (!description && !content.trim())) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const normalized = `${title}\n${description}\n${content}`.toLowerCase();
|
|
295
|
+
if (this.looksLikeNonBugRequest(normalized)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return this.looksLikeBugReport(normalized);
|
|
299
|
+
}
|
|
300
|
+
looksLikeBugReport(content) {
|
|
301
|
+
const positiveSignals = [
|
|
302
|
+
"bug",
|
|
303
|
+
"crash",
|
|
304
|
+
"error",
|
|
305
|
+
"exception",
|
|
306
|
+
"fail",
|
|
307
|
+
"failure",
|
|
308
|
+
"regression",
|
|
309
|
+
"broken",
|
|
310
|
+
"报错",
|
|
311
|
+
"崩溃",
|
|
312
|
+
"异常",
|
|
313
|
+
"失败",
|
|
314
|
+
"不生效",
|
|
315
|
+
"不工作",
|
|
316
|
+
"卡死",
|
|
317
|
+
"闪退",
|
|
318
|
+
"无法",
|
|
319
|
+
"出错"
|
|
320
|
+
];
|
|
321
|
+
return positiveSignals.some((signal) => content.includes(signal));
|
|
322
|
+
}
|
|
323
|
+
looksLikeNonBugRequest(content) {
|
|
324
|
+
const negativeSignals = [
|
|
325
|
+
"方案",
|
|
326
|
+
"建议",
|
|
327
|
+
"选型",
|
|
328
|
+
"比较",
|
|
329
|
+
"brainstorm",
|
|
330
|
+
"周报",
|
|
331
|
+
"总结",
|
|
332
|
+
"汇总",
|
|
333
|
+
"咨询",
|
|
334
|
+
"怎么做",
|
|
335
|
+
"为什么这样设计"
|
|
336
|
+
];
|
|
337
|
+
return negativeSignals.some((signal) => content.includes(signal));
|
|
338
|
+
}
|
|
339
|
+
buildCodexPrompt(event, title, content, contracts) {
|
|
340
|
+
const renderedRoles = contracts.map((contract) => [
|
|
341
|
+
`## TASK_TYPE: ${contract.taskType}`,
|
|
342
|
+
`TRIAGE_RULE_PATHS: ${contract.rulePaths.join(", ")}`,
|
|
343
|
+
contract.triageRulesMarkdown
|
|
344
|
+
].join("\n\n")).join("\n\n---\n\n");
|
|
345
|
+
return [{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: [
|
|
348
|
+
"# Triage Agent",
|
|
349
|
+
"You are the dedicated triage agent for Optimus.",
|
|
350
|
+
"Decide whether the runtime event should be accepted into one of the registered task harnesses.",
|
|
351
|
+
"You must read the candidate triage rule documents before deciding.",
|
|
352
|
+
"Do not invent task types. Only use one of the provided task types.",
|
|
353
|
+
"If none of the task types can safely process the event, reject it.",
|
|
354
|
+
"If information is missing for admission, reject it and list the missingInfo fields.",
|
|
355
|
+
"Return JSON only and satisfy the schema exactly.",
|
|
356
|
+
"",
|
|
357
|
+
"# Runtime Event",
|
|
358
|
+
JSON.stringify({
|
|
359
|
+
eventId: event.eventId,
|
|
360
|
+
type: event.type,
|
|
361
|
+
source: event.content.source,
|
|
362
|
+
title,
|
|
363
|
+
description: event.content.description ?? "",
|
|
364
|
+
content,
|
|
365
|
+
repo: event.content.repo,
|
|
366
|
+
branch: event.content.branch,
|
|
367
|
+
metadata: event.content.metadata ?? {}
|
|
368
|
+
}, null, 2),
|
|
369
|
+
"",
|
|
370
|
+
"# Candidate Task Harness Roles",
|
|
371
|
+
renderedRoles,
|
|
372
|
+
"",
|
|
373
|
+
"# Output Rules",
|
|
374
|
+
"- accepted: include decision=accepted, summary, and taskType.",
|
|
375
|
+
"- rejected: include decision=rejected, summary, reason, and optional missingInfo.",
|
|
376
|
+
"- reason should be a short machine-readable snake_case string when rejected.",
|
|
377
|
+
"- missingInfo should only contain concrete field names declared by the selected acceptance contract, such as repo, description_or_content, logs, stack_trace, expected_behavior, actual_behavior."
|
|
378
|
+
].join("\n")
|
|
379
|
+
}];
|
|
380
|
+
}
|
|
381
|
+
buildOutputSchema() {
|
|
382
|
+
return {
|
|
383
|
+
type: "object",
|
|
384
|
+
additionalProperties: false,
|
|
385
|
+
required: ["decision", "summary"],
|
|
386
|
+
properties: {
|
|
387
|
+
decision: { type: "string", enum: ["accepted", "rejected"] },
|
|
388
|
+
summary: { type: "string" },
|
|
389
|
+
taskType: { type: "string" },
|
|
390
|
+
reason: { type: "string" },
|
|
391
|
+
missingInfo: {
|
|
392
|
+
type: "array",
|
|
393
|
+
items: { type: "string" }
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
parseCodexDecision(response, contracts) {
|
|
399
|
+
let parsed;
|
|
400
|
+
try {
|
|
401
|
+
parsed = JSON.parse(response);
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
throw new TriageAgentError(`Codex triage response was not valid JSON: ${error instanceof Error ? error.message : "unknown parse error"}`, "parse_error");
|
|
405
|
+
}
|
|
406
|
+
if (!parsed || typeof parsed !== "object") {
|
|
407
|
+
throw new TriageAgentError("Codex triage response schema mismatch: root is not an object.", "schema_error");
|
|
408
|
+
}
|
|
409
|
+
const candidate = parsed;
|
|
410
|
+
if ((candidate.decision !== "accepted" && candidate.decision !== "rejected") || typeof candidate.summary !== "string" || candidate.summary.trim().length === 0) {
|
|
411
|
+
throw new TriageAgentError("Codex triage response schema mismatch: decision/summary missing.", "schema_error");
|
|
412
|
+
}
|
|
413
|
+
if (candidate.decision === "accepted") {
|
|
414
|
+
if (typeof candidate.taskType !== "string" || candidate.taskType.trim().length === 0) {
|
|
415
|
+
throw new TriageAgentError("Codex triage accepted the event but omitted taskType.", "schema_error");
|
|
416
|
+
}
|
|
417
|
+
if (!contracts.some((contract) => contract.taskType === candidate.taskType)) {
|
|
418
|
+
throw new TriageAgentError(`Codex triage selected unregistered taskType ${candidate.taskType}.`, "schema_error");
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
decision: "accepted",
|
|
422
|
+
summary: candidate.summary,
|
|
423
|
+
taskType: candidate.taskType
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const allowedMissingInfo = new Set(contracts.flatMap((contract) => contract.missingInfoFields));
|
|
427
|
+
const normalizedMissingInfo = Array.isArray(candidate.missingInfo)
|
|
428
|
+
? candidate.missingInfo
|
|
429
|
+
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
430
|
+
.map((item) => item.trim())
|
|
431
|
+
.filter((item) => allowedMissingInfo.size === 0 || allowedMissingInfo.has(item))
|
|
432
|
+
: undefined;
|
|
433
|
+
return {
|
|
434
|
+
decision: "rejected",
|
|
435
|
+
summary: candidate.summary,
|
|
436
|
+
reason: typeof candidate.reason === "string" && candidate.reason.trim().length > 0 ? candidate.reason : "unsupported_problem_type",
|
|
437
|
+
...(normalizedMissingInfo && normalizedMissingInfo.length > 0 ? { missingInfo: normalizedMissingInfo } : {})
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
parseWarmupResponse(response) {
|
|
441
|
+
let parsed;
|
|
442
|
+
try {
|
|
443
|
+
parsed = JSON.parse(response);
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
throw new TriageAgentError(`Triage warmup response was not valid JSON: ${error instanceof Error ? error.message : "unknown parse error"}`, "parse_error");
|
|
447
|
+
}
|
|
448
|
+
if (!parsed || typeof parsed !== "object") {
|
|
449
|
+
throw new TriageAgentError("Triage warmup response schema mismatch: root is not an object.", "schema_error");
|
|
450
|
+
}
|
|
451
|
+
const candidate = parsed;
|
|
452
|
+
if (typeof candidate.ready !== "boolean" || typeof candidate.summary !== "string") {
|
|
453
|
+
throw new TriageAgentError("Triage warmup response schema mismatch: ready/summary missing.", "schema_error");
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
ready: candidate.ready,
|
|
457
|
+
summary: candidate.summary
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
async runCodexTurn(input) {
|
|
461
|
+
await this.ensureCodexHome();
|
|
462
|
+
const codex = this.createCodexClient();
|
|
463
|
+
const thread = codex.startThread(this.buildThreadOptions({
|
|
464
|
+
workingDirectory: process.cwd(),
|
|
465
|
+
sandboxMode: input.sandboxMode
|
|
466
|
+
}));
|
|
467
|
+
const turn = await thread.run(input.prompt, {
|
|
468
|
+
outputSchema: input.outputSchema,
|
|
469
|
+
signal: this.createExecutionSignal(undefined, input.timeoutMs)
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
response: turn.finalResponse,
|
|
473
|
+
...(thread.id ? { sdkThreadId: thread.id } : {})
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
// Triage always runs in a read-only Codex thread because it should classify and package work, never mutate the repository.
|
|
477
|
+
buildThreadOptions(input) {
|
|
478
|
+
const options = {
|
|
479
|
+
modelReasoningEffort: this.config.codex.reasoningEffort,
|
|
480
|
+
workingDirectory: input.workingDirectory,
|
|
481
|
+
skipGitRepoCheck: true,
|
|
482
|
+
sandboxMode: input.sandboxMode,
|
|
483
|
+
approvalPolicy: this.config.codex.approvalPolicy,
|
|
484
|
+
networkAccessEnabled: this.config.codex.networkAccessEnabled
|
|
485
|
+
};
|
|
486
|
+
const effectiveModel = resolveEffectiveCodexModel(this.config);
|
|
487
|
+
if (effectiveModel) {
|
|
488
|
+
options.model = effectiveModel;
|
|
489
|
+
}
|
|
490
|
+
return options;
|
|
491
|
+
}
|
|
492
|
+
createExecutionSignal(externalSignal, timeoutMs) {
|
|
493
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
494
|
+
if (!externalSignal) {
|
|
495
|
+
return timeoutSignal;
|
|
496
|
+
}
|
|
497
|
+
if (typeof AbortSignal.any === "function") {
|
|
498
|
+
return AbortSignal.any([externalSignal, timeoutSignal]);
|
|
499
|
+
}
|
|
500
|
+
const controller = new AbortController();
|
|
501
|
+
const forwardAbort = (signal) => {
|
|
502
|
+
if (signal.aborted) {
|
|
503
|
+
controller.abort(signal.reason);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
507
|
+
};
|
|
508
|
+
forwardAbort(externalSignal);
|
|
509
|
+
forwardAbort(timeoutSignal);
|
|
510
|
+
return controller.signal;
|
|
511
|
+
}
|
|
512
|
+
// Managed CODEX_HOME ensures provider routing stays isolated from the operator's personal global Codex state.
|
|
513
|
+
async ensureCodexHome() {
|
|
514
|
+
const configText = renderManagedCodexHomeConfig(this.config);
|
|
515
|
+
if (!configText || !useManagedCodexHome(this.config)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
await mkdir(this.config.codex.homeDir, { recursive: true });
|
|
519
|
+
await writeFile(join(this.config.codex.homeDir, "config.toml"), configText, "utf8");
|
|
520
|
+
}
|
|
521
|
+
createCodexClient() {
|
|
522
|
+
const auth = new CodexAuthResolver(this.config).resolve();
|
|
523
|
+
const env = { ...auth.env };
|
|
524
|
+
if (useManagedCodexHome(this.config)) {
|
|
525
|
+
env.CODEX_HOME = this.config.codex.homeDir;
|
|
526
|
+
env.HOME = this.config.codex.homeDir;
|
|
527
|
+
}
|
|
528
|
+
const options = { env };
|
|
529
|
+
if (auth.baseUrl) {
|
|
530
|
+
options.baseUrl = auth.baseUrl;
|
|
531
|
+
}
|
|
532
|
+
if (auth.apiKey) {
|
|
533
|
+
options.apiKey = auth.apiKey;
|
|
534
|
+
}
|
|
535
|
+
const configOverrides = resolveCodexSdkConfigOverrides(this.config);
|
|
536
|
+
if (configOverrides) {
|
|
537
|
+
options.config = configOverrides;
|
|
538
|
+
}
|
|
539
|
+
return new Codex(options);
|
|
540
|
+
}
|
|
541
|
+
async writeDebugResponseArtifact(eventId, filename, content) {
|
|
542
|
+
const artifactDir = join(this.config.storage.rootDir, "artifacts", "triage");
|
|
543
|
+
await mkdir(artifactDir, { recursive: true });
|
|
544
|
+
const targetPath = join(artifactDir, `${eventId}-${filename}`);
|
|
545
|
+
await writeFile(targetPath, content, "utf8");
|
|
546
|
+
return targetPath;
|
|
547
|
+
}
|
|
548
|
+
previewResponse(response, limit = 600) {
|
|
549
|
+
const normalized = response.replace(/\s+/g, " ").trim();
|
|
550
|
+
return normalized.length > limit ? `${normalized.slice(0, limit - 3)}...` : normalized;
|
|
551
|
+
}
|
|
552
|
+
resolveFailureCategory(error) {
|
|
553
|
+
if (typeof error === "object" && error && "failureCategory" in error) {
|
|
554
|
+
const category = error.failureCategory;
|
|
555
|
+
if (category) {
|
|
556
|
+
return category;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (error instanceof Error) {
|
|
560
|
+
return classifyCodexFailureCategory(error.message);
|
|
561
|
+
}
|
|
562
|
+
return "unknown";
|
|
563
|
+
}
|
|
564
|
+
buildObjective(taskType) {
|
|
565
|
+
if (taskType === "bugfix") {
|
|
566
|
+
return "Use triage to define a bugfix task that can either produce a safe patch or conclude with a high-quality analysis closure when evidence is insufficient.";
|
|
567
|
+
}
|
|
568
|
+
return `Use triage to define a ${taskType} task.`;
|
|
569
|
+
}
|
|
570
|
+
resolveDesiredArtifactKinds(taskType) {
|
|
571
|
+
if (taskType === "bugfix") {
|
|
572
|
+
return ["analysis", "review_packet"];
|
|
573
|
+
}
|
|
574
|
+
return ["analysis"];
|
|
575
|
+
}
|
|
576
|
+
buildRequestedCapabilities(taskType) {
|
|
577
|
+
if (taskType === "bugfix") {
|
|
578
|
+
return ["problem_normalization", "context_lookup", "analysis", "patch_generation", "verification", "handoff_summary"];
|
|
579
|
+
}
|
|
580
|
+
return ["problem_normalization", "analysis"];
|
|
581
|
+
}
|
|
582
|
+
buildConstraints(event) {
|
|
583
|
+
const constraints = [
|
|
584
|
+
"Approval policy is fixed to never.",
|
|
585
|
+
"Reject events that do not contain enough information to enter the selected task harness pipeline.",
|
|
586
|
+
"Do not claim successful code changes during triage; triage only normalizes and routes work."
|
|
587
|
+
];
|
|
588
|
+
if (event.content.repo) {
|
|
589
|
+
const repoAllowed = this.config.policy.allowedRepos.length === 0 || this.config.policy.allowedRepos.includes(event.content.repo);
|
|
590
|
+
constraints.push(repoAllowed ? `Repository ${event.content.repo} is allowed by policy.` : `Repository ${event.content.repo} is outside the current allow-list.`);
|
|
591
|
+
}
|
|
592
|
+
if (this.config.policy.allowedModules.length > 0) {
|
|
593
|
+
constraints.push(`Preferred modules: ${this.config.policy.allowedModules.join(", ")}.`);
|
|
594
|
+
}
|
|
595
|
+
if (this.config.policy.blockedPaths.length > 0) {
|
|
596
|
+
constraints.push(`Blocked paths: ${this.config.policy.blockedPaths.join(", ")}.`);
|
|
597
|
+
}
|
|
598
|
+
return constraints;
|
|
599
|
+
}
|
|
600
|
+
deriveTitle(content) {
|
|
601
|
+
return content.split(/\r?\n/).map((line) => line.trim()).find(Boolean)?.slice(0, 120) ?? "Untitled task";
|
|
602
|
+
}
|
|
603
|
+
buildIdempotencyKey(title) {
|
|
604
|
+
const normalized = title.replace(/\s+/g, " ").trim().toLowerCase();
|
|
605
|
+
return `title-${createHash("sha256").update(normalized).digest("hex").slice(0, 16)}`;
|
|
606
|
+
}
|
|
607
|
+
extractSection(markdown, heading) {
|
|
608
|
+
const lines = markdown.split(/\r?\n/);
|
|
609
|
+
const headingIndex = lines.findIndex((line) => line.trim() === heading);
|
|
610
|
+
if (headingIndex === -1) {
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
const collected = [];
|
|
614
|
+
for (const line of lines.slice(headingIndex + 1)) {
|
|
615
|
+
if (line.startsWith("## ")) {
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
collected.push(line);
|
|
619
|
+
}
|
|
620
|
+
const section = collected.join("\n").trim();
|
|
621
|
+
return section || undefined;
|
|
622
|
+
}
|
|
623
|
+
extractList(markdown, heading) {
|
|
624
|
+
const section = this.extractSection(markdown, heading);
|
|
625
|
+
if (!section) {
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
return section
|
|
629
|
+
.split(/\r?\n/)
|
|
630
|
+
.map((line) => line.trim())
|
|
631
|
+
.filter((line) => line.startsWith("- "))
|
|
632
|
+
.map((line) => line.slice(2).trim())
|
|
633
|
+
.filter(Boolean);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
//# sourceMappingURL=triage-agent.js.map
|