@work-graph/cli 0.2.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/README.md +31 -0
- package/bin/work-graph.mjs +238 -0
- package/package.json +38 -0
- package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
- package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
- package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
- package/vendor/packages/workgraph-mcp/README.md +28 -0
- package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
- package/vendor/packages/workgraph-mcp/package.json +37 -0
- package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
- package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
- package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
- package/vendor/public/assets/workgraph-logo.svg +11 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
- package/vendor/public/graph-canvas-lit-flow.css +154 -0
- package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
- package/vendor/public/graph-canvas-lit-flow.js +8530 -0
- package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
- package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
- package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
- package/vendor/src/agentRunApi.mjs +136 -0
- package/vendor/src/agentToolLoopGuard.mjs +88 -0
- package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
- package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
- package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
- package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
- package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
- package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
- package/vendor/src/analyticsPanelProjection.mjs +212 -0
- package/vendor/src/analyticsRecordStore.mjs +165 -0
- package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
- package/vendor/src/architectureL1Canon.mjs +419 -0
- package/vendor/src/architectureLayout.mjs +229 -0
- package/vendor/src/architectureSnapshot.mjs +490 -0
- package/vendor/src/architectureViewsProjection.mjs +116 -0
- package/vendor/src/atomInspector.mjs +253 -0
- package/vendor/src/atomInspectorApi.mjs +130 -0
- package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
- package/vendor/src/backlogSchemaLint.mjs +176 -0
- package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
- package/vendor/src/bracketIrTraceSignal.mjs +93 -0
- package/vendor/src/bvcAtomParser.mjs +210 -0
- package/vendor/src/bvcDialectRegistry.mjs +86 -0
- package/vendor/src/bvcFileFormat.mjs +218 -0
- package/vendor/src/bvcFormatCli.mjs +55 -0
- package/vendor/src/bvcLintCli.mjs +48 -0
- package/vendor/src/bvcNewWritePolicy.mjs +70 -0
- package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
- package/vendor/src/claimNoEligibleEval.mjs +205 -0
- package/vendor/src/closingAnalysisSuggest.mjs +59 -0
- package/vendor/src/codeGapAnalyzer.mjs +308 -0
- package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
- package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
- package/vendor/src/codeGapOperatorProjection.mjs +60 -0
- package/vendor/src/codeSyntaxHighlight.mjs +123 -0
- package/vendor/src/codegenEvidence.mjs +187 -0
- package/vendor/src/compilerRoundTripCli.mjs +164 -0
- package/vendor/src/dagreGraphLayout.mjs +78 -0
- package/vendor/src/draftIntakePromotionRules.mjs +205 -0
- package/vendor/src/epicWorkScope.mjs +85 -0
- package/vendor/src/evalLiveLlmEnv.mjs +63 -0
- package/vendor/src/evidenceReadModel.mjs +167 -0
- package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
- package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
- package/vendor/src/goldenPath.mjs +69 -0
- package/vendor/src/graphCanvasLayout.mjs +464 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
- package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
- package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
- package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
- package/vendor/src/graphRagContextSlice.mjs +461 -0
- package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
- package/vendor/src/homeSnapshotApi.mjs +131 -0
- package/vendor/src/homeSnapshotProjection.mjs +275 -0
- package/vendor/src/inboxEventStream.mjs +140 -0
- package/vendor/src/intentComposerApi.mjs +245 -0
- package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
- package/vendor/src/intentGraphProjection.mjs +208 -0
- package/vendor/src/intentHierarchy.mjs +241 -0
- package/vendor/src/intentNodeLint.mjs +107 -0
- package/vendor/src/intentNodeRuntime.mjs +185 -0
- package/vendor/src/intentRoadmapCanvas.mjs +393 -0
- package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
- package/vendor/src/intentRoadmapMermaid.mjs +165 -0
- package/vendor/src/intentRoadmapProjection.mjs +85 -0
- package/vendor/src/intentTreeLint.mjs +114 -0
- package/vendor/src/intentTreeMigration.mjs +150 -0
- package/vendor/src/intentTreeWorkItems.mjs +227 -0
- package/vendor/src/kanbanBoardProjection.mjs +58 -0
- package/vendor/src/languageAdapterRegistry.mjs +180 -0
- package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
- package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
- package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
- package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
- package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
- package/vendor/src/languageAdapters/shared.mjs +68 -0
- package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
- package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
- package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
- package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
- package/vendor/src/markdownDocumentRender.mjs +208 -0
- package/vendor/src/memoryPanelProjection.mjs +116 -0
- package/vendor/src/memoryRecordWriter.mjs +243 -0
- package/vendor/src/memoryWorkerSlice.mjs +238 -0
- package/vendor/src/migrateStepToBvc.mjs +133 -0
- package/vendor/src/missionControlServerHandlers.mjs +195 -0
- package/vendor/src/missionControlUiClient.mjs +278 -0
- package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
- package/vendor/src/onebaseCliRunner.mjs +145 -0
- package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
- package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
- package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
- package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
- package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
- package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
- package/vendor/src/onebaseWorkerTools.mjs +586 -0
- package/vendor/src/operatorShellProjection.mjs +102 -0
- package/vendor/src/pipelineProseRender.mjs +180 -0
- package/vendor/src/pipelineStageLint.mjs +118 -0
- package/vendor/src/promptRulesEditorApi.mjs +174 -0
- package/vendor/src/promptRulesProjection.mjs +134 -0
- package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
- package/vendor/src/pvrgTaskScope.mjs +152 -0
- package/vendor/src/releaseGateMatrix.mjs +188 -0
- package/vendor/src/schematicView.mjs +305 -0
- package/vendor/src/seedAnalyticsRecord.mjs +217 -0
- package/vendor/src/semanticSearchBm25.mjs +103 -0
- package/vendor/src/semanticSearchExcerpts.mjs +68 -0
- package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
- package/vendor/src/semanticSearchWorkflow.mjs +366 -0
- package/vendor/src/stepAtomFormatter.mjs +413 -0
- package/vendor/src/stepGraphSlice.mjs +318 -0
- package/vendor/src/ui/atoms/badge.mjs +40 -0
- package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
- package/vendor/src/ui/atoms/button.mjs +114 -0
- package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
- package/vendor/src/ui/atoms/icon.mjs +23 -0
- package/vendor/src/ui/atoms/input.mjs +38 -0
- package/vendor/src/ui/atoms/modal.mjs +44 -0
- package/vendor/src/ui/atoms/select.mjs +98 -0
- package/vendor/src/ui/backlogShellButtons.mjs +238 -0
- package/vendor/src/ui/htmlEscape.mjs +11 -0
- package/vendor/src/ui/molecules/rating.mjs +48 -0
- package/vendor/src/ui/molecules/tabs.mjs +70 -0
- package/vendor/src/ui/organisms/modal.mjs +1 -0
- package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
- package/vendor/src/ui/workItemStatusTone.mjs +36 -0
- package/vendor/src/unifiedLinkageProjection.mjs +264 -0
- package/vendor/src/verificationLoop.mjs +206 -0
- package/vendor/src/workGraphBacklogPersist.mjs +234 -0
- package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
- package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
- package/vendor/src/workGraphCycleSlice.mjs +184 -0
- package/vendor/src/workGraphDaemonTick.mjs +307 -0
- package/vendor/src/workGraphDaemonWatch.mjs +157 -0
- package/vendor/src/workGraphEngineRoot.mjs +136 -0
- package/vendor/src/workGraphInstallLayout.mjs +65 -0
- package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
- package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
- package/vendor/src/workGraphProjectHost.mjs +149 -0
- package/vendor/src/workGraphProjectInit.mjs +392 -0
- package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
- package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
- package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
- package/vendor/src/workGraphRuntime.mjs +1008 -0
- package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
- package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
- package/vendor/src/workGraphWorkerProvider.mjs +600 -0
- package/vendor/src/workItemBvcQuality.mjs +262 -0
- package/vendor/src/workItemCreateAnalysis.mjs +157 -0
- package/vendor/src/workItemDecisionPipeline.mjs +278 -0
- package/vendor/src/workItemEpicCascade.mjs +176 -0
- package/vendor/src/workItemExecutionGate.mjs +78 -0
- package/vendor/src/workItemHierarchy.mjs +226 -0
- package/vendor/src/workItemProseLint.mjs +133 -0
- package/vendor/src/workItemTextRusify.mjs +794 -0
- package/vendor/src/workItemTraceEnvelope.mjs +158 -0
- package/vendor/src/workItemUiReferences.mjs +272 -0
- package/vendor/src/workflowEpicGrouping.mjs +67 -0
- package/vendor/src/workflowTreeProjection.mjs +53 -0
- package/vendor/src/workspaceRegistry.mjs +150 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
|
|
3
|
+
import { readBvcTextFile } from './bvcFileFormat.mjs';
|
|
4
|
+
import { claimNext, parseWorkItems } from './workGraphRuntime.mjs';
|
|
5
|
+
import { buildGraphRagContextForWorkerInput } from './graphRagContextSlice.mjs';
|
|
6
|
+
import { buildMemoryWorkerSliceForTask } from './memoryWorkerSlice.mjs';
|
|
7
|
+
import {
|
|
8
|
+
resolveOnebaseAllowedTools,
|
|
9
|
+
runOnebaseWorkerPreflight,
|
|
10
|
+
} from './onebaseWorkerTools.mjs';
|
|
11
|
+
import { runGvmVerifyPreflight, isGvmVerifyEnabled } from './gvmVerifyWorkerGate.mjs';
|
|
12
|
+
|
|
13
|
+
const INPUT_SCHEMA = 'agent-worker.input.v1';
|
|
14
|
+
const OUTPUT_SCHEMA = 'agent-worker.output.v1';
|
|
15
|
+
const DEFAULT_BACKLOG_PATH = 'work/backlog.bvc';
|
|
16
|
+
|
|
17
|
+
export function buildWorkerInputFromTask(task, options = {}) {
|
|
18
|
+
assertTask(task);
|
|
19
|
+
|
|
20
|
+
const runId = options.runId ?? `local-${task.id}`;
|
|
21
|
+
const policy = {
|
|
22
|
+
mode: 'dry-run',
|
|
23
|
+
allowShell: false,
|
|
24
|
+
allowNetwork: false,
|
|
25
|
+
allowFileWrite: false,
|
|
26
|
+
timeoutMs: 0,
|
|
27
|
+
...(options.policy ?? {}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const memorySlice = [...(options.memorySlice ?? [])];
|
|
31
|
+
if (Array.isArray(options.workGraphItems) && options.workGraphItems.length > 0) {
|
|
32
|
+
const memoryWorkerSlice = buildMemoryWorkerSliceForTask(
|
|
33
|
+
options.workGraphItems,
|
|
34
|
+
task.id,
|
|
35
|
+
options.memoryWorker,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (memoryWorkerSlice.recordCount > 0) {
|
|
39
|
+
memorySlice.push(memoryWorkerSlice);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
memorySlice.push(buildGraphRagContextForWorkerInput(
|
|
43
|
+
options.workGraphItems,
|
|
44
|
+
task.id,
|
|
45
|
+
options.graphRag,
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
schema: INPUT_SCHEMA,
|
|
51
|
+
runId,
|
|
52
|
+
task: {
|
|
53
|
+
id: task.id,
|
|
54
|
+
title: task.title,
|
|
55
|
+
status: task.status,
|
|
56
|
+
department: task.department ?? '',
|
|
57
|
+
labels: { ...(task.labels ?? {}) },
|
|
58
|
+
checks: [...task.checks],
|
|
59
|
+
evidence: [...task.evidence],
|
|
60
|
+
dependsOn: [...task.dependsOn],
|
|
61
|
+
targetFiles: [...task.targetFiles],
|
|
62
|
+
traceStatus: task.traceStatus,
|
|
63
|
+
nextAction: task.nextAction,
|
|
64
|
+
},
|
|
65
|
+
memorySlice,
|
|
66
|
+
allowedTools: options.allowedTools ?? resolveOnebaseAllowedTools(task),
|
|
67
|
+
targetFiles: [...task.targetFiles],
|
|
68
|
+
policy,
|
|
69
|
+
providerHints: {
|
|
70
|
+
provider: 'local-runner',
|
|
71
|
+
deterministic: true,
|
|
72
|
+
...(options.providerHints ?? {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createWorkerInputFromBacklogText(backlogText, options = {}) {
|
|
78
|
+
const items = parseWorkItems(backlogText);
|
|
79
|
+
const task = selectTask(items, options.taskId);
|
|
80
|
+
|
|
81
|
+
if (task === null) {
|
|
82
|
+
throw new Error(options.taskId === undefined ? 'no ready Work Graph task is claimable' : `task not found: ${options.taskId}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return buildWorkerInputFromTask(task, options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function createWorkerInputFromBacklogFile(options = {}) {
|
|
89
|
+
const cwd = options.cwd ?? process.cwd();
|
|
90
|
+
const backlogPath = options.backlogPath ?? DEFAULT_BACKLOG_PATH;
|
|
91
|
+
const backlogText = await readBvcTextFile(backlogPath, { cwd });
|
|
92
|
+
return createWorkerInputFromBacklogText(backlogText, options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function runLocalWorker(input) {
|
|
96
|
+
const validationError = validateWorkerInput(input);
|
|
97
|
+
if (validationError !== null) {
|
|
98
|
+
return buildFailureOutput(input, validationError);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (input.policy.mode !== 'dry-run') {
|
|
102
|
+
return buildFailureOutput(input, `unsupported local runner mode: ${input.policy.mode}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const onebasePreflight = runOnebaseWorkerPreflight(input.task, {
|
|
106
|
+
policy: input.policy,
|
|
107
|
+
onebaseRoot: input.providerHints?.onebaseRoot,
|
|
108
|
+
});
|
|
109
|
+
const gvmPreflight = runGvmVerifyPreflight({
|
|
110
|
+
cwd: input.providerHints?.cwd,
|
|
111
|
+
env: input.providerHints?.env,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const evidence = [
|
|
115
|
+
{
|
|
116
|
+
kind: 'worker_run',
|
|
117
|
+
source: 'local-runner',
|
|
118
|
+
result: 'succeeded',
|
|
119
|
+
summary: `Dry-run accepted Work Graph task ${input.task.id}.`,
|
|
120
|
+
},
|
|
121
|
+
...onebasePreflight.evidence,
|
|
122
|
+
...gvmPreflight.evidence,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const onebaseSummary = onebasePreflight.skipped
|
|
126
|
+
? ''
|
|
127
|
+
: ` OneBase preflight: metadata=${onebasePreflight.summary?.metadataTotal ?? 0}, staticVerify=${onebasePreflight.summary?.staticVerifyOk ? 'ok' : 'fail'}.`;
|
|
128
|
+
const gvmSummary = isGvmVerifyEnabled({ env: input.providerHints?.env })
|
|
129
|
+
? ` GVM preflight: ${gvmPreflight.status}.`
|
|
130
|
+
: '';
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
schema: OUTPUT_SCHEMA,
|
|
134
|
+
runId: input.runId,
|
|
135
|
+
taskId: input.task.id,
|
|
136
|
+
status: 'succeeded',
|
|
137
|
+
patchSummary: {
|
|
138
|
+
changedFiles: [],
|
|
139
|
+
summary: `No files changed; local runner executed in deterministic dry-run mode.${onebaseSummary}${gvmSummary}`,
|
|
140
|
+
},
|
|
141
|
+
evidence,
|
|
142
|
+
transitionRequest: {
|
|
143
|
+
status: 'verify',
|
|
144
|
+
reason: 'local runner dry-run completed',
|
|
145
|
+
},
|
|
146
|
+
logs: [
|
|
147
|
+
{
|
|
148
|
+
level: 'info',
|
|
149
|
+
message: `Prepared task ${input.task.id} with ${input.targetFiles.length} target file(s).`,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
failureReason: '',
|
|
153
|
+
retryAdvice: '',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function runLocalWorkerFromBacklogFile(options = {}) {
|
|
158
|
+
return runLocalWorker(await createWorkerInputFromBacklogFile(options));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function selectTask(items, taskId) {
|
|
162
|
+
if (taskId === undefined || taskId === '' || taskId === '--next') {
|
|
163
|
+
return claimNext(items);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return items.find((item) => item.id === taskId) ?? null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function validateWorkerInput(input) {
|
|
170
|
+
if (!input || typeof input !== 'object') {
|
|
171
|
+
return 'input must be an object';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (input.schema !== INPUT_SCHEMA) {
|
|
175
|
+
return `unsupported input schema: ${String(input.schema)}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (typeof input.runId !== 'string' || input.runId.trim() === '') {
|
|
179
|
+
return 'runId must be a non-empty string';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!input.task || typeof input.task.id !== 'string' || input.task.id.trim() === '') {
|
|
183
|
+
return 'task.id must be a non-empty string';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!input.policy || input.policy.allowShell !== false || input.policy.allowNetwork !== false || input.policy.allowFileWrite !== false) {
|
|
187
|
+
return 'local runner MVP requires shell, network and file writes to be disabled';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildFailureOutput(input, failureReason) {
|
|
194
|
+
const runId = typeof input?.runId === 'string' && input.runId.trim() !== '' ? input.runId : 'local-invalid-input';
|
|
195
|
+
const taskId = typeof input?.task?.id === 'string' && input.task.id.trim() !== '' ? input.task.id : '';
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
schema: OUTPUT_SCHEMA,
|
|
199
|
+
runId,
|
|
200
|
+
taskId,
|
|
201
|
+
status: 'failed',
|
|
202
|
+
patchSummary: {
|
|
203
|
+
changedFiles: [],
|
|
204
|
+
summary: 'No files changed; local runner stopped before execution.',
|
|
205
|
+
},
|
|
206
|
+
evidence: [
|
|
207
|
+
{
|
|
208
|
+
kind: 'worker_run',
|
|
209
|
+
source: 'local-runner',
|
|
210
|
+
result: 'failed',
|
|
211
|
+
summary: failureReason,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
transitionRequest: {
|
|
215
|
+
status: 'blocked',
|
|
216
|
+
reason: failureReason,
|
|
217
|
+
},
|
|
218
|
+
logs: [{ level: 'error', message: failureReason }],
|
|
219
|
+
failureReason,
|
|
220
|
+
retryAdvice: 'Rebuild Worker Input v1 from the current Work Graph snapshot and retry in dry-run mode.',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function assertTask(task) {
|
|
225
|
+
if (!task || typeof task !== 'object' || typeof task.id !== 'string') {
|
|
226
|
+
throw new TypeError('task must be a parsed WorkItem');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const field of ['checks', 'evidence', 'dependsOn', 'targetFiles']) {
|
|
230
|
+
if (!Array.isArray(task[field])) {
|
|
231
|
+
throw new TypeError('task must be a parsed WorkItem');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
237
|
+
const taskId = process.argv.find((arg, index) => index > 1 && !arg.startsWith('--'));
|
|
238
|
+
const backlogFlagIndex = process.argv.indexOf('--backlog');
|
|
239
|
+
const backlogPath = backlogFlagIndex === -1 ? DEFAULT_BACKLOG_PATH : process.argv[backlogFlagIndex + 1];
|
|
240
|
+
const output = await runLocalWorkerFromBacklogFile({ taskId, backlogPath });
|
|
241
|
+
|
|
242
|
+
console.log(JSON.stringify(output, null, 2));
|
|
243
|
+
if (output.status !== 'succeeded') {
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBoundedTargetFilesForPrompt,
|
|
3
|
+
readBoundedTargetFiles,
|
|
4
|
+
} from './workGraphBoundedTargetFileRead.mjs';
|
|
5
|
+
import {
|
|
6
|
+
formatGraphRagContextForPrompt,
|
|
7
|
+
} from './graphRagContextSlice.mjs';
|
|
8
|
+
import { formatMemoryWorkerSliceForPrompt } from './memoryWorkerSlice.mjs';
|
|
9
|
+
import {
|
|
10
|
+
buildTargetFileFactsProjection,
|
|
11
|
+
formatLanguageFileFactsForPrompt,
|
|
12
|
+
} from './languageAdapterRegistry.mjs';
|
|
13
|
+
import { resolveRoleChainHandoff } from './workGraphToolSurfaceAudit.mjs';
|
|
14
|
+
|
|
15
|
+
const INPUT_SCHEMA = 'agent-worker.input.v1';
|
|
16
|
+
const OUTPUT_SCHEMA = 'agent-worker.output.v1';
|
|
17
|
+
|
|
18
|
+
export const WORKER_NATIVE_TOOL_CALLS_ENV = 'IOHASC_WORKER_NATIVE_TOOL_CALLS';
|
|
19
|
+
|
|
20
|
+
export const WORKER_OPENAI_NATIVE_TOOL_NAMES = [
|
|
21
|
+
'submit_worker_output',
|
|
22
|
+
'read_target_file',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function resolveWorkerNativeToolCallsEnabled(options = {}) {
|
|
26
|
+
const env = options.env ?? process.env;
|
|
27
|
+
const raw = String(options.nativeToolCalls ?? env[WORKER_NATIVE_TOOL_CALLS_ENV] ?? '').trim();
|
|
28
|
+
return raw === '1' || raw.toLowerCase() === 'true';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildOpenAiWorkerToolDefinitions(input, options = {}) {
|
|
32
|
+
const allowed = new Set([
|
|
33
|
+
...(Array.isArray(input?.allowedTools) ? input.allowedTools : []),
|
|
34
|
+
...(Array.isArray(options.extraTools) ? options.extraTools : []),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const tools = [{
|
|
38
|
+
type: 'function',
|
|
39
|
+
function: {
|
|
40
|
+
name: 'submit_worker_output',
|
|
41
|
+
description: 'Submit the final agent-worker.output.v1 payload when the task plan is ready.',
|
|
42
|
+
parameters: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
output: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
description: 'Object matching agent-worker.output.v1',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ['output'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}];
|
|
54
|
+
|
|
55
|
+
if (allowed.has('onebase.readConfigFile') || allowed.has('onebase.listMetadata') || (input?.targetFiles?.length ?? 0) > 0) {
|
|
56
|
+
tools.push({
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: {
|
|
59
|
+
name: 'read_target_file',
|
|
60
|
+
description: 'Read one bounded target file path from the current task allowlist.',
|
|
61
|
+
parameters: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
path: { type: 'string', description: 'Relative path from task targetFiles' },
|
|
65
|
+
},
|
|
66
|
+
required: ['path'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const toolName of allowed) {
|
|
73
|
+
if (!toolName.startsWith('onebase.') || tools.some((tool) => tool.function.name === toolName.replaceAll('.', '_'))) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
tools.push({
|
|
78
|
+
type: 'function',
|
|
79
|
+
function: {
|
|
80
|
+
name: toolName.replaceAll('.', '_'),
|
|
81
|
+
description: `Invoke bounded OneBase worker tool ${toolName} (advisory; no side effects in OpenAI worker MVP).`,
|
|
82
|
+
parameters: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
note: { type: 'string', description: 'Why this tool is requested' },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return tools.slice(0, options.maxTools ?? 8);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildOpenAiChatCompletionRequestBody(prompt, options = {}) {
|
|
96
|
+
const env = options.env ?? resolveOpenAiProviderEnv(options);
|
|
97
|
+
const body = {
|
|
98
|
+
model: env.model,
|
|
99
|
+
messages: prompt.messages,
|
|
100
|
+
temperature: 0,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (env.nativeToolCallsEnabled) {
|
|
104
|
+
body.tools = buildOpenAiWorkerToolDefinitions(options.input, options);
|
|
105
|
+
body.tool_choice = 'auto';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return body;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function parseWorkerOutputFromToolCalls(message, input) {
|
|
112
|
+
const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
|
|
113
|
+
const submitCall = toolCalls.find((call) => call?.function?.name === 'submit_worker_output');
|
|
114
|
+
|
|
115
|
+
if (!submitCall?.function?.arguments) {
|
|
116
|
+
throw new Error('model response did not include submit_worker_output tool call');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parsed = JSON.parse(submitCall.function.arguments);
|
|
120
|
+
const rawOutput = parsed?.output && typeof parsed.output === 'object' ? parsed.output : parsed;
|
|
121
|
+
return normalizeWorkerOutput(rawOutput, input);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildWorkerPromptFromInput(input, options = {}) {
|
|
125
|
+
if (!input || typeof input !== 'object') {
|
|
126
|
+
throw new TypeError('input must be an object');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const task = input.task ?? {};
|
|
130
|
+
const checks = Array.isArray(task.checks) ? task.checks : [];
|
|
131
|
+
const targetFiles = Array.isArray(input.targetFiles) ? input.targetFiles : [];
|
|
132
|
+
const graphRagPrompt = (input.memorySlice ?? [])
|
|
133
|
+
.map((entry) => {
|
|
134
|
+
if (entry?.schema === 'pvrg.graph_rag.context.v1') {
|
|
135
|
+
return formatGraphRagContextForPrompt(entry);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return formatMemoryWorkerSliceForPrompt(entry);
|
|
139
|
+
})
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join('\n\n');
|
|
142
|
+
|
|
143
|
+
const systemParts = [
|
|
144
|
+
'You are a Work Graph agent worker adapter.',
|
|
145
|
+
'Respond with a single JSON object matching agent-worker.output.v1.',
|
|
146
|
+
'Do not mutate files directly; summarize proposed changes in patchSummary.',
|
|
147
|
+
'Allowed transitionRequest.status values: verify, blocked, ready.',
|
|
148
|
+
'Set status to succeeded only when the task plan is complete enough for verification.',
|
|
149
|
+
'The status field must be exactly one of: succeeded, failed, cancelled, needs_human (never ready, verify, or done).',
|
|
150
|
+
'Example: {"schema":"agent-worker.output.v1","runId":"...","taskId":"...","status":"succeeded","patchSummary":{"changedFiles":[],"summary":"..."},"evidence":[{"kind":"worker_run","source":"openai-compatible","result":"succeeded","summary":"..."}],"transitionRequest":{"status":"verify","reason":"..."}}',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
if (input.providerHints?.behaviorRulesPrompt) {
|
|
154
|
+
systemParts.push('Behavior rules (prompt_rule bundle):');
|
|
155
|
+
systemParts.push(String(input.providerHints.behaviorRulesPrompt));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const system = systemParts.join(' ');
|
|
159
|
+
|
|
160
|
+
const user = [
|
|
161
|
+
`Task id: ${task.id ?? ''}`,
|
|
162
|
+
`Title: ${task.title ?? ''}`,
|
|
163
|
+
`Status: ${task.status ?? ''}`,
|
|
164
|
+
`Next action: ${task.nextAction ?? ''}`,
|
|
165
|
+
`Checks:\n${checks.map((entry) => `- ${entry}`).join('\n') || '- none'}`,
|
|
166
|
+
`Target files:\n${targetFiles.map((entry) => `- ${entry}`).join('\n') || '- none'}`,
|
|
167
|
+
graphRagPrompt,
|
|
168
|
+
options.targetFileContents
|
|
169
|
+
? `Target file contents (bounded read):\n${options.targetFileContents}`
|
|
170
|
+
: '',
|
|
171
|
+
`Policy mode: ${input.policy?.mode ?? 'dry-run'}`,
|
|
172
|
+
'Return JSON fields: schema, runId, taskId, status, patchSummary, evidence, transitionRequest, logs, failureReason, retryAdvice.',
|
|
173
|
+
].filter(Boolean).join('\n');
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
schema: 'agent-worker.prompt.v1',
|
|
177
|
+
messages: [
|
|
178
|
+
{ role: 'system', content: system },
|
|
179
|
+
{ role: 'user', content: user },
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function resolveOpenAiProviderEnv(options = {}) {
|
|
185
|
+
const env = options.env ?? process.env;
|
|
186
|
+
return {
|
|
187
|
+
baseUrl: String(options.baseUrl ?? env.IOHASC_LLM_BASE_URL ?? 'http://127.0.0.1:1234/v1').replace(/\/+$/u, ''),
|
|
188
|
+
model: String(options.model ?? env.IOHASC_LLM_MODEL ?? 'local-model'),
|
|
189
|
+
apiKey: String(options.apiKey ?? env.IOHASC_LLM_API_KEY ?? ''),
|
|
190
|
+
liveEnabled: (options.liveEnabled ?? env.IOHASC_E2E_REAL_LLM) === '1',
|
|
191
|
+
nativeToolCallsEnabled: resolveWorkerNativeToolCallsEnabled(options),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function extractFirstJsonObject(text) {
|
|
196
|
+
const trimmed = String(text ?? '').trim();
|
|
197
|
+
const start = trimmed.indexOf('{');
|
|
198
|
+
if (start === -1) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let depth = 0;
|
|
203
|
+
let inString = false;
|
|
204
|
+
let escaped = false;
|
|
205
|
+
|
|
206
|
+
for (let index = start; index < trimmed.length; index += 1) {
|
|
207
|
+
const char = trimmed[index];
|
|
208
|
+
|
|
209
|
+
if (inString) {
|
|
210
|
+
if (escaped) {
|
|
211
|
+
escaped = false;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (char === '\\') {
|
|
216
|
+
escaped = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (char === '"') {
|
|
221
|
+
inString = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (char === '"') {
|
|
228
|
+
inString = true;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (char === '{') {
|
|
233
|
+
depth += 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (char === '}') {
|
|
238
|
+
depth -= 1;
|
|
239
|
+
if (depth === 0) {
|
|
240
|
+
return trimmed.slice(start, index + 1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function parseWorkerOutputFromModelText(text, input) {
|
|
249
|
+
const jsonText = extractFirstJsonObject(text);
|
|
250
|
+
|
|
251
|
+
if (!jsonText) {
|
|
252
|
+
throw new Error('model response did not contain JSON object');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const parsed = JSON.parse(jsonText);
|
|
256
|
+
return normalizeWorkerOutput(parsed, input);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function normalizeWorkerOutput(raw, input) {
|
|
260
|
+
const runId = typeof raw?.runId === 'string' && raw.runId.trim() !== ''
|
|
261
|
+
? raw.runId
|
|
262
|
+
: input.runId;
|
|
263
|
+
const taskId = typeof raw?.taskId === 'string' && raw.taskId.trim() !== ''
|
|
264
|
+
? raw.taskId
|
|
265
|
+
: input.task.id;
|
|
266
|
+
const rawStatus = typeof raw?.status === 'string' ? raw.status.trim() : '';
|
|
267
|
+
let status = ['succeeded', 'failed', 'cancelled', 'needs_human'].includes(rawStatus)
|
|
268
|
+
? rawStatus
|
|
269
|
+
: 'failed';
|
|
270
|
+
|
|
271
|
+
if (status === 'failed' && ['ready', 'verify', 'done'].includes(rawStatus)) {
|
|
272
|
+
status = 'succeeded';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const patchSummaryRaw = raw?.patchSummary;
|
|
276
|
+
const patchSummary = typeof patchSummaryRaw === 'string'
|
|
277
|
+
? { changedFiles: [], summary: patchSummaryRaw }
|
|
278
|
+
: {
|
|
279
|
+
changedFiles: Array.isArray(patchSummaryRaw?.changedFiles) ? patchSummaryRaw.changedFiles : [],
|
|
280
|
+
summary: String(patchSummaryRaw?.summary ?? raw?.summary ?? 'OpenAI-compatible worker response'),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
schema: OUTPUT_SCHEMA,
|
|
285
|
+
runId,
|
|
286
|
+
taskId,
|
|
287
|
+
status,
|
|
288
|
+
patchSummary,
|
|
289
|
+
evidence: Array.isArray(raw?.evidence) && raw.evidence.length > 0
|
|
290
|
+
? raw.evidence
|
|
291
|
+
: [{
|
|
292
|
+
kind: 'worker_run',
|
|
293
|
+
source: 'openai-compatible',
|
|
294
|
+
result: status === 'succeeded' ? 'succeeded' : 'failed',
|
|
295
|
+
summary: String(raw?.patchSummary?.summary ?? raw?.summary ?? 'OpenAI-compatible worker run'),
|
|
296
|
+
}],
|
|
297
|
+
transitionRequest: {
|
|
298
|
+
status: ['verify', 'done', 'blocked', 'ready'].includes(raw?.transitionRequest?.status)
|
|
299
|
+
? raw.transitionRequest.status
|
|
300
|
+
: (status === 'succeeded' ? 'verify' : 'blocked'),
|
|
301
|
+
reason: String(raw?.transitionRequest?.reason ?? raw?.failureReason ?? ''),
|
|
302
|
+
},
|
|
303
|
+
logs: Array.isArray(raw?.logs) ? raw.logs : [{ level: 'info', message: 'OpenAI-compatible worker completed' }],
|
|
304
|
+
failureReason: String(raw?.failureReason ?? (status === 'succeeded' ? '' : 'model returned non-success status')),
|
|
305
|
+
retryAdvice: String(raw?.retryAdvice ?? 'Retry with a model that supports structured JSON output.'),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function runOpenAiCompatibleWorker(input, options = {}) {
|
|
310
|
+
const validationError = validateOpenAiWorkerInput(input);
|
|
311
|
+
if (validationError !== null) {
|
|
312
|
+
return buildOpenAiFailureOutput(input, validationError, 'code_failure');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const env = resolveOpenAiProviderEnv(options);
|
|
316
|
+
if (!env.liveEnabled && options.requireLive !== false) {
|
|
317
|
+
return buildOpenAiFailureOutput(
|
|
318
|
+
input,
|
|
319
|
+
'OpenAI-compatible provider skipped: set IOHASC_E2E_REAL_LLM=1 for live path',
|
|
320
|
+
'skipped',
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const includeTargetFiles = options.includeTargetFiles !== false;
|
|
325
|
+
let targetFileContents = '';
|
|
326
|
+
if (includeTargetFiles && (input.targetFiles?.length ?? 0) > 0) {
|
|
327
|
+
const readResult = await readBoundedTargetFiles(input, {
|
|
328
|
+
repoRoot: options.repoRoot ?? options.cwd,
|
|
329
|
+
readFile: options.readFile,
|
|
330
|
+
maxBytesPerFile: options.maxBytesPerFile,
|
|
331
|
+
maxTotalBytes: options.maxTotalBytes,
|
|
332
|
+
});
|
|
333
|
+
const factsBlock = formatLanguageFileFactsForPrompt(buildTargetFileFactsProjection(readResult));
|
|
334
|
+
targetFileContents = formatBoundedTargetFilesForPrompt(readResult);
|
|
335
|
+
if (factsBlock) {
|
|
336
|
+
targetFileContents = `${targetFileContents}\n\n${factsBlock}`;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const prompt = buildWorkerPromptFromInput(input, { targetFileContents });
|
|
341
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
342
|
+
if (typeof fetchImpl !== 'function') {
|
|
343
|
+
return buildOpenAiFailureOutput(input, 'fetch is not available in this runtime', 'env_blocker');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
347
|
+
if (env.apiKey.trim() !== '') {
|
|
348
|
+
headers.Authorization = `Bearer ${env.apiKey.trim()}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let response;
|
|
352
|
+
try {
|
|
353
|
+
response = await fetchImpl(`${env.baseUrl}/chat/completions`, {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
headers,
|
|
356
|
+
body: JSON.stringify(buildOpenAiChatCompletionRequestBody(prompt, {
|
|
357
|
+
...options,
|
|
358
|
+
input,
|
|
359
|
+
env,
|
|
360
|
+
})),
|
|
361
|
+
signal: options.signal,
|
|
362
|
+
});
|
|
363
|
+
} catch (error) {
|
|
364
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
365
|
+
return buildOpenAiFailureOutput(input, `OpenAI-compatible request failed: ${message}`, 'env_blocker');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
const body = await response.text();
|
|
370
|
+
return buildOpenAiFailureOutput(
|
|
371
|
+
input,
|
|
372
|
+
`OpenAI-compatible HTTP ${response.status}: ${body.slice(0, 500)}`,
|
|
373
|
+
'model_failure',
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const payload = await response.json();
|
|
378
|
+
const message = payload?.choices?.[0]?.message;
|
|
379
|
+
const content = message?.content;
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
if (env.nativeToolCallsEnabled && Array.isArray(message?.tool_calls) && message.tool_calls.length > 0) {
|
|
383
|
+
return parseWorkerOutputFromToolCalls(message, input);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return parseWorkerOutputFromModelText(content, input);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
+
return buildOpenAiFailureOutput(input, `Failed to parse model JSON: ${message}`, 'model_failure');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function buildWorkerInputWithRoleHandoff(task, options = {}) {
|
|
394
|
+
const handoff = resolveRoleChainHandoff(task.ownerRole, options.handoff ?? {});
|
|
395
|
+
return {
|
|
396
|
+
runId: options.runId,
|
|
397
|
+
policy: handoff.policy,
|
|
398
|
+
providerHints: {
|
|
399
|
+
provider: options.provider ?? 'openai-compatible',
|
|
400
|
+
...handoff.providerHints,
|
|
401
|
+
},
|
|
402
|
+
allowedTools: options.allowedTools,
|
|
403
|
+
memorySlice: options.memorySlice,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function validateOpenAiWorkerInput(input) {
|
|
408
|
+
if (!input || typeof input !== 'object') {
|
|
409
|
+
return 'input must be an object';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (input.schema !== INPUT_SCHEMA) {
|
|
413
|
+
return `unsupported input schema: ${String(input.schema)}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!input.task || typeof input.task.id !== 'string' || input.task.id.trim() === '') {
|
|
417
|
+
return 'task.id must be a non-empty string';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (input.policy?.allowShell === true || input.policy?.allowFileWrite === true) {
|
|
421
|
+
return 'openai-compatible provider MVP rejects shell/file-write policy flags';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function buildOpenAiFailureOutput(input, failureReason, failureClass) {
|
|
428
|
+
const runId = typeof input?.runId === 'string' && input.runId.trim() !== '' ? input.runId : 'openai-invalid-input';
|
|
429
|
+
const taskId = typeof input?.task?.id === 'string' && input.task.id.trim() !== '' ? input.task.id : '';
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
schema: OUTPUT_SCHEMA,
|
|
433
|
+
runId,
|
|
434
|
+
taskId,
|
|
435
|
+
status: failureClass === 'skipped' ? 'failed' : 'failed',
|
|
436
|
+
patchSummary: {
|
|
437
|
+
changedFiles: [],
|
|
438
|
+
summary: failureClass === 'skipped'
|
|
439
|
+
? 'OpenAI-compatible provider skipped (contract-only mode).'
|
|
440
|
+
: 'OpenAI-compatible provider failed before producing a patch summary.',
|
|
441
|
+
},
|
|
442
|
+
evidence: [{
|
|
443
|
+
kind: 'worker_run',
|
|
444
|
+
source: 'openai-compatible',
|
|
445
|
+
result: 'failed',
|
|
446
|
+
summary: failureReason,
|
|
447
|
+
failureClass,
|
|
448
|
+
}],
|
|
449
|
+
transitionRequest: {
|
|
450
|
+
status: 'blocked',
|
|
451
|
+
reason: failureReason,
|
|
452
|
+
},
|
|
453
|
+
logs: [{ level: failureClass === 'skipped' ? 'info' : 'error', message: failureReason }],
|
|
454
|
+
failureReason,
|
|
455
|
+
retryAdvice: failureClass === 'skipped'
|
|
456
|
+
? 'Set IOHASC_E2E_REAL_LLM=1 and configure IOHASC_LLM_BASE_URL / IOHASC_LLM_MODEL.'
|
|
457
|
+
: 'Verify endpoint availability and model JSON output quality, then retry.',
|
|
458
|
+
};
|
|
459
|
+
}
|