@twelvehart/orcats 0.0.0-seed.0 → 0.2.3
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/LICENSE +48 -0
- package/NOTICE +5 -0
- package/README.md +399 -0
- package/bin/orcats +4 -0
- package/dist/backends/acp-client.d.ts +74 -0
- package/dist/backends/acp-client.d.ts.map +1 -0
- package/dist/backends/acp-run.d.ts +31 -0
- package/dist/backends/acp-run.d.ts.map +1 -0
- package/dist/backends/claude-run.d.ts +35 -0
- package/dist/backends/claude-run.d.ts.map +1 -0
- package/dist/backends/claude-stream-json.d.ts +15 -0
- package/dist/backends/claude-stream-json.d.ts.map +1 -0
- package/dist/backends/codex-jsonl.d.ts +34 -0
- package/dist/backends/codex-jsonl.d.ts.map +1 -0
- package/dist/backends/codex-run.d.ts +40 -0
- package/dist/backends/codex-run.d.ts.map +1 -0
- package/dist/backends/codex.d.ts +5 -0
- package/dist/backends/codex.d.ts.map +1 -0
- package/dist/backends/conversation-config.d.ts +12 -0
- package/dist/backends/conversation-config.d.ts.map +1 -0
- package/dist/backends/index.d.ts +14 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/opencode-run.d.ts +48 -0
- package/dist/backends/opencode-run.d.ts.map +1 -0
- package/dist/backends/opencode-sse.d.ts +35 -0
- package/dist/backends/opencode-sse.d.ts.map +1 -0
- package/dist/backends/pi-rpc.d.ts +21 -0
- package/dist/backends/pi-rpc.d.ts.map +1 -0
- package/dist/backends/pi-run.d.ts +21 -0
- package/dist/backends/pi-run.d.ts.map +1 -0
- package/dist/backends/select.d.ts +17 -0
- package/dist/backends/select.d.ts.map +1 -0
- package/dist/backends/subprocess-run.d.ts +68 -0
- package/dist/backends/subprocess-run.d.ts.map +1 -0
- package/dist/backends/types.d.ts +17 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/unsupported.d.ts +9 -0
- package/dist/backends/unsupported.d.ts.map +1 -0
- package/dist/baseline/index.d.ts +56 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/cli/args.d.ts +22 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/embedded.d.ts +7 -0
- package/dist/cli/embedded.d.ts.map +1 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/conversation/ask-user.d.ts +17 -0
- package/dist/conversation/ask-user.d.ts.map +1 -0
- package/dist/conversation/conversation.d.ts +46 -0
- package/dist/conversation/conversation.d.ts.map +1 -0
- package/dist/conversation/harness.d.ts +14 -0
- package/dist/conversation/harness.d.ts.map +1 -0
- package/dist/conversation/index.d.ts +5 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/queue.d.ts +15 -0
- package/dist/conversation/queue.d.ts.map +1 -0
- package/dist/flow/accessors.d.ts +13 -0
- package/dist/flow/accessors.d.ts.map +1 -0
- package/dist/flow/approvals.d.ts +6 -0
- package/dist/flow/approvals.d.ts.map +1 -0
- package/dist/flow/args.d.ts +4 -0
- package/dist/flow/args.d.ts.map +1 -0
- package/dist/flow/context.d.ts +36 -0
- package/dist/flow/context.d.ts.map +1 -0
- package/dist/flow/index.d.ts +5 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/loop/builder/index.d.ts +17 -0
- package/dist/loop/builder/index.d.ts.map +1 -0
- package/dist/loop/builder/types.d.ts +97 -0
- package/dist/loop/builder/types.d.ts.map +1 -0
- package/dist/loop/context/compaction.d.ts +38 -0
- package/dist/loop/context/compaction.d.ts.map +1 -0
- package/dist/loop/context/index.d.ts +4 -0
- package/dist/loop/context/index.d.ts.map +1 -0
- package/dist/loop/context/offload.d.ts +34 -0
- package/dist/loop/context/offload.d.ts.map +1 -0
- package/dist/loop/context/types.d.ts +13 -0
- package/dist/loop/context/types.d.ts.map +1 -0
- package/dist/loop/engine/bridge.d.ts +21 -0
- package/dist/loop/engine/bridge.d.ts.map +1 -0
- package/dist/loop/engine/index.d.ts +73 -0
- package/dist/loop/engine/index.d.ts.map +1 -0
- package/dist/loop/execution.d.ts +68 -0
- package/dist/loop/execution.d.ts.map +1 -0
- package/dist/loop/fanout.d.ts +148 -0
- package/dist/loop/fanout.d.ts.map +1 -0
- package/dist/loop/firing.d.ts +54 -0
- package/dist/loop/firing.d.ts.map +1 -0
- package/dist/loop/graph/analyze.d.ts +8 -0
- package/dist/loop/graph/analyze.d.ts.map +1 -0
- package/dist/loop/graph/index.d.ts +3 -0
- package/dist/loop/graph/index.d.ts.map +1 -0
- package/dist/loop/graph/types.d.ts +35 -0
- package/dist/loop/graph/types.d.ts.map +1 -0
- package/dist/loop/index.d.ts +7 -0
- package/dist/loop/index.d.ts.map +1 -0
- package/dist/loop/io/index.d.ts +4 -0
- package/dist/loop/io/index.d.ts.map +1 -0
- package/dist/loop/io/linear.d.ts +142 -0
- package/dist/loop/io/linear.d.ts.map +1 -0
- package/dist/loop/io/sink.d.ts +47 -0
- package/dist/loop/io/sink.d.ts.map +1 -0
- package/dist/loop/io/source.d.ts +64 -0
- package/dist/loop/io/source.d.ts.map +1 -0
- package/dist/loop/serve.d.ts +97 -0
- package/dist/loop/serve.d.ts.map +1 -0
- package/dist/loop/state/index.d.ts +5 -0
- package/dist/loop/state/index.d.ts.map +1 -0
- package/dist/loop/state/manifest.d.ts +36 -0
- package/dist/loop/state/manifest.d.ts.map +1 -0
- package/dist/loop/state/port.d.ts +33 -0
- package/dist/loop/state/port.d.ts.map +1 -0
- package/dist/loop/state/snapshot.d.ts +15 -0
- package/dist/loop/state/snapshot.d.ts.map +1 -0
- package/dist/loop/state/sqlite.d.ts +31 -0
- package/dist/loop/state/sqlite.d.ts.map +1 -0
- package/dist/loop/termination-contract.d.ts +28 -0
- package/dist/loop/termination-contract.d.ts.map +1 -0
- package/dist/loop/termination.d.ts +33 -0
- package/dist/loop/termination.d.ts.map +1 -0
- package/dist/model/backend-config.d.ts +30 -0
- package/dist/model/backend-config.d.ts.map +1 -0
- package/dist/model/brand.d.ts +9 -0
- package/dist/model/brand.d.ts.map +1 -0
- package/dist/model/errors.d.ts +11 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/index.d.ts +8 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/json-schema.d.ts +4 -0
- package/dist/model/json-schema.d.ts.map +1 -0
- package/dist/model/result.d.ts +3 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/schemas.d.ts +435 -0
- package/dist/model/schemas.d.ts.map +1 -0
- package/dist/model/structured.d.ts +9 -0
- package/dist/model/structured.d.ts.map +1 -0
- package/dist/monitor/index.d.ts +139 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/plan/index.d.ts +3 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/persistent.d.ts +23 -0
- package/dist/plan/persistent.d.ts.map +1 -0
- package/dist/plan/verdict.d.ts +9 -0
- package/dist/plan/verdict.d.ts.map +1 -0
- package/dist/review/index.d.ts +4 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/loop.d.ts +117 -0
- package/dist/review/loop.d.ts.map +1 -0
- package/dist/review/reviewers.d.ts +13 -0
- package/dist/review/reviewers.d.ts.map +1 -0
- package/dist/review/tool.d.ts +20 -0
- package/dist/review/tool.d.ts.map +1 -0
- package/dist/run-output/index.d.ts +112 -0
- package/dist/run-output/index.d.ts.map +1 -0
- package/dist/runner/index.d.ts +2 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/typecheck.d.ts +23 -0
- package/dist/runner/typecheck.d.ts.map +1 -0
- package/dist/test-utils/fakes.d.ts +76 -0
- package/dist/test-utils/fakes.d.ts.map +1 -0
- package/dist/test-utils/fixtures.d.ts +4 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/tier2.d.ts +8 -0
- package/dist/test-utils/tier2.d.ts.map +1 -0
- package/dist/tools/fs.d.ts +11 -0
- package/dist/tools/fs.d.ts.map +1 -0
- package/dist/tools/git.d.ts +10 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/github.d.ts +13 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/linear.d.ts +117 -0
- package/dist/tools/linear.d.ts.map +1 -0
- package/dist/tools/process.d.ts +40 -0
- package/dist/tools/process.d.ts.map +1 -0
- package/dist/tools/terminal.d.ts +23 -0
- package/dist/tools/terminal.d.ts.map +1 -0
- package/package.json +89 -9
- package/src/backends/acp-client.ts +324 -0
- package/src/backends/acp-run.ts +636 -0
- package/src/backends/claude-run.ts +226 -0
- package/src/backends/claude-stream-json.ts +273 -0
- package/src/backends/codex-jsonl.ts +339 -0
- package/src/backends/codex-run.ts +177 -0
- package/src/backends/codex.ts +84 -0
- package/src/backends/conversation-config.ts +26 -0
- package/src/backends/index.ts +13 -0
- package/src/backends/opencode-run.ts +432 -0
- package/src/backends/opencode-sse.ts +360 -0
- package/src/backends/pi-rpc.ts +242 -0
- package/src/backends/pi-run.ts +154 -0
- package/src/backends/select.ts +77 -0
- package/src/backends/subprocess-run.ts +305 -0
- package/src/backends/types.ts +29 -0
- package/src/backends/unsupported.ts +48 -0
- package/src/baseline/index.ts +437 -0
- package/src/cli/args.ts +109 -0
- package/src/cli/embedded.ts +206 -0
- package/src/cli/main.ts +332 -0
- package/src/cli/version.ts +1 -0
- package/src/conversation/ask-user.ts +105 -0
- package/src/conversation/conversation.ts +109 -0
- package/src/conversation/harness.ts +39 -0
- package/src/conversation/index.ts +4 -0
- package/src/conversation/queue.ts +86 -0
- package/src/flow/accessors.ts +43 -0
- package/src/flow/approvals.ts +10 -0
- package/src/flow/args.ts +26 -0
- package/src/flow/context.ts +106 -0
- package/src/flow/index.ts +4 -0
- package/src/index.ts +17 -0
- package/src/loop/builder/index.ts +346 -0
- package/src/loop/builder/types.ts +131 -0
- package/src/loop/context/compaction.ts +175 -0
- package/src/loop/context/index.ts +3 -0
- package/src/loop/context/offload.ts +79 -0
- package/src/loop/context/types.ts +17 -0
- package/src/loop/engine/bridge.ts +61 -0
- package/src/loop/engine/index.ts +166 -0
- package/src/loop/execution.ts +453 -0
- package/src/loop/fanout.ts +444 -0
- package/src/loop/firing.ts +188 -0
- package/src/loop/graph/analyze.ts +128 -0
- package/src/loop/graph/index.ts +2 -0
- package/src/loop/graph/types.ts +45 -0
- package/src/loop/index.ts +11 -0
- package/src/loop/io/index.ts +3 -0
- package/src/loop/io/linear.ts +714 -0
- package/src/loop/io/sink.ts +170 -0
- package/src/loop/io/source.ts +255 -0
- package/src/loop/serve.ts +307 -0
- package/src/loop/state/index.ts +4 -0
- package/src/loop/state/manifest.ts +56 -0
- package/src/loop/state/port.ts +43 -0
- package/src/loop/state/snapshot.ts +129 -0
- package/src/loop/state/sqlite.ts +265 -0
- package/src/loop/termination-contract.ts +89 -0
- package/src/loop/termination.ts +156 -0
- package/src/model/backend-config.ts +34 -0
- package/src/model/brand.ts +11 -0
- package/src/model/errors.ts +28 -0
- package/src/model/index.ts +7 -0
- package/src/model/json-schema.ts +14 -0
- package/src/model/result.ts +28 -0
- package/src/model/schemas.ts +218 -0
- package/src/model/structured.ts +26 -0
- package/src/monitor/index.ts +367 -0
- package/src/plan/index.ts +2 -0
- package/src/plan/persistent.ts +98 -0
- package/src/plan/verdict.ts +3 -0
- package/src/review/index.ts +3 -0
- package/src/review/loop.ts +365 -0
- package/src/review/reviewers.ts +92 -0
- package/src/review/tool.ts +41 -0
- package/src/run-output/index.ts +319 -0
- package/src/runner/index.ts +1 -0
- package/src/runner/typecheck.ts +72 -0
- package/src/test-utils/fakes.ts +260 -0
- package/src/test-utils/fixtures.ts +33 -0
- package/src/test-utils/index.ts +3 -0
- package/src/test-utils/tier2.ts +12 -0
- package/src/tools/fs.ts +44 -0
- package/src/tools/git.ts +34 -0
- package/src/tools/github.ts +25 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/linear.ts +428 -0
- package/src/tools/process.ts +156 -0
- package/src/tools/terminal.ts +116 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const TokenCountSchema = z.number().int().nonnegative();
|
|
4
|
+
const ExitCodeSchema = z.number().int().nullable();
|
|
5
|
+
|
|
6
|
+
export const BackendTagSchema = z.enum([
|
|
7
|
+
"claude",
|
|
8
|
+
"codex",
|
|
9
|
+
"opencode",
|
|
10
|
+
"pi"
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export const UsageSchema = z.object({
|
|
14
|
+
input: TokenCountSchema,
|
|
15
|
+
output: TokenCountSchema,
|
|
16
|
+
reasoning: TokenCountSchema.optional()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const AssistantTextDeltaSchema = z.object({
|
|
20
|
+
type: z.literal("assistant_text_delta"),
|
|
21
|
+
text: z.string()
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const AssistantTurnEndSchema = z.object({
|
|
25
|
+
type: z.literal("assistant_turn_end")
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const AssistantThinkingDeltaSchema = z.object({
|
|
29
|
+
type: z.literal("assistant_thinking_delta"),
|
|
30
|
+
text: z.string()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const AssistantToolCallSchema = z.object({
|
|
34
|
+
type: z.literal("assistant_tool_call"),
|
|
35
|
+
id: z.string(),
|
|
36
|
+
name: z.string(),
|
|
37
|
+
input: z.unknown()
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const ToolResultSchema = z.object({
|
|
41
|
+
type: z.literal("tool_result"),
|
|
42
|
+
toolCallId: z.string(),
|
|
43
|
+
output: z.unknown(),
|
|
44
|
+
isError: z.boolean().optional()
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const ConversationErrorSchema = z.object({
|
|
48
|
+
type: z.literal("error"),
|
|
49
|
+
code: z.string().optional(),
|
|
50
|
+
message: z.string()
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const UserPromptSchema = z.object({
|
|
54
|
+
type: z.literal("user_prompt"),
|
|
55
|
+
text: z.string()
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const UserQuestionSchema = z.object({
|
|
59
|
+
type: z.literal("user_question"),
|
|
60
|
+
question: z.string()
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const ApproveToolSchema = z.object({
|
|
64
|
+
type: z.literal("approve_tool"),
|
|
65
|
+
toolCallId: z.string(),
|
|
66
|
+
name: z.string(),
|
|
67
|
+
input: z.unknown()
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
71
|
+
AssistantTextDeltaSchema,
|
|
72
|
+
AssistantTurnEndSchema,
|
|
73
|
+
AssistantThinkingDeltaSchema,
|
|
74
|
+
AssistantToolCallSchema,
|
|
75
|
+
ToolResultSchema,
|
|
76
|
+
ConversationErrorSchema,
|
|
77
|
+
UserPromptSchema,
|
|
78
|
+
UserQuestionSchema,
|
|
79
|
+
ApproveToolSchema
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
export const StructuredResultEventSchema = z.object({
|
|
83
|
+
type: z.literal("structured_result"),
|
|
84
|
+
raw: z.unknown(),
|
|
85
|
+
summary: z.string().optional()
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const ToolUseEventSchema = z.object({
|
|
89
|
+
type: z.literal("tool_use"),
|
|
90
|
+
name: z.string(),
|
|
91
|
+
input: z.unknown()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const AssistantMessageEventSchema = z.object({
|
|
95
|
+
type: z.literal("assistant_message"),
|
|
96
|
+
text: z.string()
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const TokensUsedEventSchema = z.object({
|
|
100
|
+
type: z.literal("tokens_used"),
|
|
101
|
+
usage: UsageSchema
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const StepEventSchema = z.object({
|
|
105
|
+
type: z.literal("step"),
|
|
106
|
+
name: z.string(),
|
|
107
|
+
status: z.enum(["started", "completed", "failed"])
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const OrcaEventSchema = z.discriminatedUnion("type", [
|
|
111
|
+
UserPromptSchema,
|
|
112
|
+
ToolUseEventSchema,
|
|
113
|
+
AssistantMessageEventSchema,
|
|
114
|
+
TokensUsedEventSchema,
|
|
115
|
+
StructuredResultEventSchema,
|
|
116
|
+
StepEventSchema,
|
|
117
|
+
ConversationErrorSchema
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
export const LlmResultSchema = z.object({
|
|
121
|
+
backend: BackendTagSchema,
|
|
122
|
+
sessionId: z.string(),
|
|
123
|
+
output: z.string(),
|
|
124
|
+
structured: z.unknown().optional(),
|
|
125
|
+
usage: UsageSchema.optional()
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const NothingToCommitErrorSchema = z.object({
|
|
129
|
+
_tag: z.literal("NothingToCommit")
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const BranchAlreadyExistsErrorSchema = z.object({
|
|
133
|
+
_tag: z.literal("BranchAlreadyExists"),
|
|
134
|
+
branch: z.string()
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const PushRejectedErrorSchema = z.object({
|
|
138
|
+
_tag: z.literal("PushRejected"),
|
|
139
|
+
remote: z.string().optional(),
|
|
140
|
+
stderr: z.string()
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const CommandFailedErrorSchema = z.object({
|
|
144
|
+
_tag: z.literal("CommandFailed"),
|
|
145
|
+
command: z.string(),
|
|
146
|
+
exitCode: ExitCodeSchema,
|
|
147
|
+
stdout: z.string(),
|
|
148
|
+
stderr: z.string()
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export const StructuredOutputValidationFailedErrorSchema = z.object({
|
|
152
|
+
_tag: z.literal("StructuredOutputValidationFailed"),
|
|
153
|
+
issues: z.array(z.string()),
|
|
154
|
+
raw: z.unknown()
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const UnsupportedFeatureErrorSchema = z.object({
|
|
158
|
+
_tag: z.literal("UnsupportedFeature"),
|
|
159
|
+
feature: z.string(),
|
|
160
|
+
reason: z.string()
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const BackendFailedErrorSchema = z.object({
|
|
164
|
+
_tag: z.literal("BackendFailed"),
|
|
165
|
+
backend: BackendTagSchema,
|
|
166
|
+
message: z.string()
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const TypecheckFailedErrorSchema = z.object({
|
|
170
|
+
_tag: z.literal("TypecheckFailed"),
|
|
171
|
+
stdout: z.string(),
|
|
172
|
+
stderr: z.string(),
|
|
173
|
+
exitCode: ExitCodeSchema
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
export const FileSystemErrorSchema = z.object({
|
|
177
|
+
_tag: z.literal("FileSystemError"),
|
|
178
|
+
path: z.string(),
|
|
179
|
+
message: z.string()
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Runtime IO seams report failures here. `kind` is modelled as a plain string so this schema stays
|
|
183
|
+
// free of tool and loop-module dependencies (model must not import either).
|
|
184
|
+
export const IoFailedErrorSchema = z.object({
|
|
185
|
+
_tag: z.literal("IoFailed"),
|
|
186
|
+
seam: z.enum(["source", "sink", "tool"]),
|
|
187
|
+
kind: z.string(),
|
|
188
|
+
message: z.string()
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export const RuntimeErrorSchema = z.discriminatedUnion("_tag", [
|
|
192
|
+
NothingToCommitErrorSchema,
|
|
193
|
+
BranchAlreadyExistsErrorSchema,
|
|
194
|
+
PushRejectedErrorSchema,
|
|
195
|
+
CommandFailedErrorSchema,
|
|
196
|
+
StructuredOutputValidationFailedErrorSchema,
|
|
197
|
+
UnsupportedFeatureErrorSchema,
|
|
198
|
+
BackendFailedErrorSchema,
|
|
199
|
+
TypecheckFailedErrorSchema,
|
|
200
|
+
FileSystemErrorSchema,
|
|
201
|
+
IoFailedErrorSchema
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
export type BackendTag = z.infer<typeof BackendTagSchema>;
|
|
205
|
+
export type Usage = z.infer<typeof UsageSchema>;
|
|
206
|
+
export type ConversationEvent = z.infer<typeof ConversationEventSchema>;
|
|
207
|
+
export type OrcaEvent = z.infer<typeof OrcaEventSchema>;
|
|
208
|
+
export type LlmResult = z.infer<typeof LlmResultSchema>;
|
|
209
|
+
export type RuntimeError = z.infer<typeof RuntimeErrorSchema>;
|
|
210
|
+
|
|
211
|
+
export const CanonicalSchemas = {
|
|
212
|
+
BackendTag: BackendTagSchema,
|
|
213
|
+
Usage: UsageSchema,
|
|
214
|
+
ConversationEvent: ConversationEventSchema,
|
|
215
|
+
OrcaEvent: OrcaEventSchema,
|
|
216
|
+
LlmResult: LlmResultSchema,
|
|
217
|
+
RuntimeError: RuntimeErrorSchema
|
|
218
|
+
} as const;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { err, ok, type Result } from "neverthrow";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
import { structuredOutputValidationFailed } from "./errors.ts";
|
|
4
|
+
import type { RuntimeError } from "./schemas.ts";
|
|
5
|
+
|
|
6
|
+
export interface StructuredOutput<T> {
|
|
7
|
+
readonly raw: unknown;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseStructuredOutput<T>(
|
|
12
|
+
schema: ZodType<T>,
|
|
13
|
+
raw: unknown
|
|
14
|
+
): Result<StructuredOutput<T>, RuntimeError> {
|
|
15
|
+
const parsed = schema.safeParse(raw);
|
|
16
|
+
if (parsed.success) {
|
|
17
|
+
return ok({ raw, value: parsed.data });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return err(
|
|
21
|
+
structuredOutputValidationFailed({
|
|
22
|
+
raw,
|
|
23
|
+
issues: parsed.error.issues.map(({ message }) => message)
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import type { Usage } from "../model/index.ts";
|
|
5
|
+
import { TokenBudgetCounter, type TokenUsageSummary } from "../loop/termination.ts";
|
|
6
|
+
import type { LoopStopReason } from "../loop/builder/types.ts";
|
|
7
|
+
import type { LoopContextPressure } from "../loop/execution.ts";
|
|
8
|
+
import {
|
|
9
|
+
createRunPresenter,
|
|
10
|
+
createRunReporter,
|
|
11
|
+
type CycleBranchEvent,
|
|
12
|
+
type RunEvent,
|
|
13
|
+
type RunReporter,
|
|
14
|
+
} from "../run-output/index.ts";
|
|
15
|
+
|
|
16
|
+
export type StageStatus = "completed" | "failed";
|
|
17
|
+
/** Discriminated cleanup verdict. `clean`/`repaired` are safe improvements
|
|
18
|
+
* (pass); `regressed`/`guard-reject` are reverted changes (fail); `declined`
|
|
19
|
+
* is a neutral no-op; `precondition-skip` is excluded from the backend's
|
|
20
|
+
* denominator because the file's gate was already red before the agent ran. */
|
|
21
|
+
export type OutcomeVerdict =
|
|
22
|
+
| "clean"
|
|
23
|
+
| "repaired"
|
|
24
|
+
| "regressed"
|
|
25
|
+
| "guard-reject"
|
|
26
|
+
| "declined"
|
|
27
|
+
| "precondition-skip";
|
|
28
|
+
|
|
29
|
+
/** Why a `regressed` change could not be made to pass the gate. */
|
|
30
|
+
export type RegressedReason = "stuck" | "timeout" | "ceiling";
|
|
31
|
+
|
|
32
|
+
export interface StageLog {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly startedAt: string;
|
|
35
|
+
readonly durationMs: number;
|
|
36
|
+
readonly status: StageStatus;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CommandLog {
|
|
40
|
+
readonly command: string;
|
|
41
|
+
readonly status: "passed" | "failed";
|
|
42
|
+
readonly stdout: string;
|
|
43
|
+
readonly stderr: string;
|
|
44
|
+
readonly exitCode: number | null;
|
|
45
|
+
readonly durationMs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OutcomeLog {
|
|
49
|
+
readonly file: string;
|
|
50
|
+
readonly verdict: OutcomeVerdict;
|
|
51
|
+
readonly durationMs: number;
|
|
52
|
+
readonly smellsRemoved: readonly string[];
|
|
53
|
+
readonly changedPaths?: readonly string[];
|
|
54
|
+
readonly validation?: readonly CommandLog[];
|
|
55
|
+
readonly reason?: string;
|
|
56
|
+
/** Repair iterations to reach green: 0 for `clean`, K for `repaired`. */
|
|
57
|
+
readonly iterations?: number;
|
|
58
|
+
/** Set only when `verdict === "regressed"`. */
|
|
59
|
+
readonly regressedReason?: RegressedReason;
|
|
60
|
+
/** Total agent tokens spent on this file (initial edit + repairs). */
|
|
61
|
+
readonly tokens?: number;
|
|
62
|
+
readonly usage?: Usage;
|
|
63
|
+
readonly snapshotPath?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FailureLog {
|
|
67
|
+
readonly file: string;
|
|
68
|
+
readonly error: unknown;
|
|
69
|
+
readonly durationMs: number;
|
|
70
|
+
readonly category?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WorkflowRunSummary {
|
|
74
|
+
/** Safe improvements: `clean` + `repaired`. */
|
|
75
|
+
readonly pass: number;
|
|
76
|
+
/** Reverted changes (`regressed` + `guard-reject`) plus thrown failures. */
|
|
77
|
+
readonly fail: number;
|
|
78
|
+
/** Neutral no-ops: `declined`. */
|
|
79
|
+
readonly skip: number;
|
|
80
|
+
/** Files whose gate was already red before the agent — excluded from the
|
|
81
|
+
* pass-rate denominator (`pass + fail + skip`). */
|
|
82
|
+
readonly preconditionSkip: number;
|
|
83
|
+
readonly durationMs: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Per-cycle progress stream (spec execution-observability / tasks §9). The stream is derived from
|
|
87
|
+
// the same manifest projection (L05 `measure`) that drives the termination variant, so the reported
|
|
88
|
+
// `measure`/`delta` cannot drift from the stop reason. Cumulative usage reuses the L02
|
|
89
|
+
// `TokenBudgetCounter`, so a cycle with no backend usage surfaces as `unknown`, never zero.
|
|
90
|
+
|
|
91
|
+
/** Stop status known at a cycle. `running` = no terminal reason yet — the incipient-runaway
|
|
92
|
+
* window where a flat `delta` with rising `cumulativeUsage` is visible before a guard fires. */
|
|
93
|
+
export type CycleStopStatus = LoopStopReason | "running";
|
|
94
|
+
|
|
95
|
+
/** One branch of a fan-out cycle. Missing backend usage is `unknown`, not zero. */
|
|
96
|
+
export interface BranchProgress {
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly status: StageStatus;
|
|
99
|
+
readonly usage: Usage | "unknown";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A completed cycle's progress record, appended to the run log. */
|
|
103
|
+
export interface CycleProgress {
|
|
104
|
+
readonly iteration: number;
|
|
105
|
+
readonly measure: number;
|
|
106
|
+
/** Change in `measure` vs the prior recorded cycle (`0` for the first). */
|
|
107
|
+
readonly delta: number;
|
|
108
|
+
readonly stopReasonSoFar: CycleStopStatus;
|
|
109
|
+
/** Present only for a fan-out cycle. */
|
|
110
|
+
readonly branches?: readonly BranchProgress[];
|
|
111
|
+
/** Cumulative reported usage across cycles; `unknown` (not zero) once any cycle reports none. */
|
|
112
|
+
readonly cumulativeUsage: TokenUsageSummary;
|
|
113
|
+
/** Context pressure evidence from loop execution when offload or compaction ran. */
|
|
114
|
+
readonly contextPressure?: LoopContextPressure;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A single branch of a fan-out cycle as observed by the caller; `usage` omitted ⇒ `unknown`. */
|
|
118
|
+
export interface BranchObservation {
|
|
119
|
+
readonly id: string;
|
|
120
|
+
readonly status: StageStatus;
|
|
121
|
+
readonly usage?: Usage;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Caller input for {@link WorkflowMonitor.recordCycle}; `delta` and `cumulativeUsage` are derived. */
|
|
125
|
+
export interface CycleObservation {
|
|
126
|
+
readonly iteration: number;
|
|
127
|
+
/** Current measure from the manifest projection / loop variant. */
|
|
128
|
+
readonly measure: number;
|
|
129
|
+
/** Total backend usage reported this cycle (non-fanout); omit ⇒ cumulative becomes `unknown`. */
|
|
130
|
+
readonly usage?: Usage;
|
|
131
|
+
/** Per-branch records for a fan-out cycle; when present, cumulative usage folds each branch. */
|
|
132
|
+
readonly branches?: readonly BranchObservation[];
|
|
133
|
+
/** Terminal stop reason for the loop-ending cycle. Defaults to a value derived from the variant:
|
|
134
|
+
* `converged` when `measure <= floor`, else `running`. */
|
|
135
|
+
readonly stopReason?: LoopStopReason;
|
|
136
|
+
/** Convergence floor for the derived stop status; default `0` (the loop builder's floor). */
|
|
137
|
+
readonly floor?: number;
|
|
138
|
+
/** Context pressure evidence emitted by loop execution for this cycle. */
|
|
139
|
+
readonly contextPressure?: LoopContextPressure;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface WorkflowRunLog {
|
|
143
|
+
readonly runId: string;
|
|
144
|
+
readonly startedAt: string;
|
|
145
|
+
readonly backend: string;
|
|
146
|
+
readonly stages: readonly StageLog[];
|
|
147
|
+
readonly outcomes: readonly OutcomeLog[];
|
|
148
|
+
readonly failures: readonly FailureLog[];
|
|
149
|
+
readonly summary: WorkflowRunSummary;
|
|
150
|
+
/** Per-cycle progress stream; empty for a non-loop run. */
|
|
151
|
+
readonly progress: readonly CycleProgress[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface WorkflowMonitorOptions {
|
|
155
|
+
readonly writeStatus?: (line: string) => void;
|
|
156
|
+
readonly reporter?: RunReporter;
|
|
157
|
+
readonly statusIntervalMs?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const DefaultStatusIntervalMs = 30_000;
|
|
161
|
+
|
|
162
|
+
export class WorkflowMonitor {
|
|
163
|
+
readonly #runId: string;
|
|
164
|
+
readonly #startedAt: Date;
|
|
165
|
+
readonly #backend: string;
|
|
166
|
+
readonly #reporter: RunReporter;
|
|
167
|
+
readonly #statusIntervalMs: number;
|
|
168
|
+
readonly #stages: StageLog[] = [];
|
|
169
|
+
readonly #outcomes: OutcomeLog[] = [];
|
|
170
|
+
readonly #failures: FailureLog[] = [];
|
|
171
|
+
readonly #progress: CycleProgress[] = [];
|
|
172
|
+
readonly #cumulativeUsage = new TokenBudgetCounter();
|
|
173
|
+
#lastMeasure: number | undefined;
|
|
174
|
+
|
|
175
|
+
constructor(backend: string, options: WorkflowMonitorOptions = {}) {
|
|
176
|
+
this.#runId = randomUUID();
|
|
177
|
+
this.#startedAt = new Date();
|
|
178
|
+
this.#backend = backend;
|
|
179
|
+
this.#reporter = options.reporter ?? defaultRunReporter(options.writeStatus);
|
|
180
|
+
this.#statusIntervalMs = options.statusIntervalMs ?? DefaultStatusIntervalMs;
|
|
181
|
+
this.#emit({ type: "run_started", runId: this.#runId, backend });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
get runId(): string {
|
|
185
|
+
return this.#runId;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async stage<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
|
189
|
+
const startedAt = new Date().toISOString();
|
|
190
|
+
const start = Date.now();
|
|
191
|
+
const heartbeat = this.#startHeartbeat(name, start);
|
|
192
|
+
this.#emit({ type: "stage", name, status: "started" });
|
|
193
|
+
try {
|
|
194
|
+
const result = await fn();
|
|
195
|
+
const durationMs = Date.now() - start;
|
|
196
|
+
this.#stages.push({ name, startedAt, durationMs, status: "completed" });
|
|
197
|
+
this.#emit({ type: "stage", name, status: "completed", durationMs });
|
|
198
|
+
return result;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const durationMs = Date.now() - start;
|
|
201
|
+
this.#stages.push({ name, startedAt, durationMs, status: "failed" });
|
|
202
|
+
this.#emit({ type: "stage", name, status: "failed", durationMs, message: describeError(error) });
|
|
203
|
+
throw error;
|
|
204
|
+
} finally {
|
|
205
|
+
if (heartbeat !== undefined) {
|
|
206
|
+
clearInterval(heartbeat);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
recordOutcome(log: OutcomeLog): void {
|
|
212
|
+
this.#outcomes.push(log);
|
|
213
|
+
this.#emit({
|
|
214
|
+
type: "outcome",
|
|
215
|
+
file: log.file,
|
|
216
|
+
verdict: log.verdict,
|
|
217
|
+
durationMs: log.durationMs,
|
|
218
|
+
...(log.reason === undefined ? {} : { reason: log.reason }),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
recordFailure(log: FailureLog): void {
|
|
223
|
+
this.#failures.push(log);
|
|
224
|
+
this.#emit({
|
|
225
|
+
type: "failure",
|
|
226
|
+
file: log.file,
|
|
227
|
+
...(log.category === undefined ? {} : { category: log.category }),
|
|
228
|
+
durationMs: log.durationMs,
|
|
229
|
+
message: describeError(log.error),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Append a per-cycle progress record. `delta` is derived against the prior cycle's `measure`
|
|
234
|
+
* and `cumulativeUsage` is folded with the L02 token counter, so both stay consistent with the
|
|
235
|
+
* termination variant and a usage-less cycle reports `unknown` rather than zero. */
|
|
236
|
+
recordCycle(observation: CycleObservation): void {
|
|
237
|
+
if (observation.branches !== undefined) {
|
|
238
|
+
for (const branch of observation.branches) {
|
|
239
|
+
this.#cumulativeUsage.record(branch.usage);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
this.#cumulativeUsage.record(observation.usage);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const delta = this.#lastMeasure === undefined ? 0 : observation.measure - this.#lastMeasure;
|
|
246
|
+
this.#lastMeasure = observation.measure;
|
|
247
|
+
|
|
248
|
+
const floor = observation.floor ?? 0;
|
|
249
|
+
const stopReasonSoFar: CycleStopStatus =
|
|
250
|
+
observation.stopReason ?? (observation.measure <= floor ? "converged" : "running");
|
|
251
|
+
|
|
252
|
+
this.#progress.push({
|
|
253
|
+
iteration: observation.iteration,
|
|
254
|
+
measure: observation.measure,
|
|
255
|
+
delta,
|
|
256
|
+
stopReasonSoFar,
|
|
257
|
+
...(observation.branches === undefined
|
|
258
|
+
? {}
|
|
259
|
+
: { branches: observation.branches.map(toBranchProgress) }),
|
|
260
|
+
cumulativeUsage: this.#cumulativeUsage.summary(),
|
|
261
|
+
...(observation.contextPressure === undefined ? {} : { contextPressure: observation.contextPressure }),
|
|
262
|
+
});
|
|
263
|
+
this.#emit({
|
|
264
|
+
type: "cycle_progress",
|
|
265
|
+
iteration: observation.iteration,
|
|
266
|
+
measure: observation.measure,
|
|
267
|
+
delta,
|
|
268
|
+
stopStatus: stopReasonSoFar,
|
|
269
|
+
...(observation.usage === undefined ? {} : { usage: observation.usage }),
|
|
270
|
+
...(observation.branches === undefined
|
|
271
|
+
? {}
|
|
272
|
+
: { branches: observation.branches.map(toRunEventBranch) }),
|
|
273
|
+
...(observation.contextPressure === undefined ? {} : { contextPressure: observation.contextPressure }),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
toJson(): WorkflowRunLog {
|
|
278
|
+
const count = (verdict: OutcomeVerdict): number =>
|
|
279
|
+
this.#outcomes.filter((outcome) => outcome.verdict === verdict).length;
|
|
280
|
+
const pass = count("clean") + count("repaired");
|
|
281
|
+
const fail = count("regressed") + count("guard-reject") + this.#failures.length;
|
|
282
|
+
const skip = count("declined");
|
|
283
|
+
const preconditionSkip = count("precondition-skip");
|
|
284
|
+
return {
|
|
285
|
+
runId: this.#runId,
|
|
286
|
+
startedAt: this.#startedAt.toISOString(),
|
|
287
|
+
backend: this.#backend,
|
|
288
|
+
stages: [...this.#stages],
|
|
289
|
+
outcomes: [...this.#outcomes],
|
|
290
|
+
failures: [...this.#failures],
|
|
291
|
+
summary: { pass, fail, skip, preconditionSkip, durationMs: Date.now() - this.#startedAt.getTime() },
|
|
292
|
+
progress: [...this.#progress],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async writeLog(logDir: string): Promise<void> {
|
|
297
|
+
const path = join(logDir, `${this.#runId}.json`);
|
|
298
|
+
await mkdir(dirname(path), { recursive: true });
|
|
299
|
+
await writeFile(path, JSON.stringify(this.toJson(), null, 2));
|
|
300
|
+
this.#emit({ type: "artifact", artifact: "monitor-log", path });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#startHeartbeat(name: string, start: number): ReturnType<typeof setInterval> | undefined {
|
|
304
|
+
if (this.#statusIntervalMs <= 0) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
const heartbeat = setInterval(() => {
|
|
308
|
+
this.#emit({ type: "stage", name, status: "running", durationMs: Date.now() - start });
|
|
309
|
+
}, this.#statusIntervalMs);
|
|
310
|
+
heartbeat.unref();
|
|
311
|
+
return heartbeat;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#emit(event: RunEvent): void {
|
|
315
|
+
try {
|
|
316
|
+
this.#reporter.emit(event);
|
|
317
|
+
} catch {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function toBranchProgress(branch: BranchObservation): BranchProgress {
|
|
324
|
+
return { id: branch.id, status: branch.status, usage: branch.usage ?? "unknown" };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function toRunEventBranch(branch: BranchObservation): CycleBranchEvent {
|
|
328
|
+
return { id: branch.id, status: branch.status, usage: branch.usage ?? "unknown" };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function defaultRunReporter(writeStatus: ((line: string) => void) | undefined): RunReporter {
|
|
332
|
+
const writer = writeStatus ?? defaultStatusWriter();
|
|
333
|
+
if (writer === undefined) {
|
|
334
|
+
return createRunReporter();
|
|
335
|
+
}
|
|
336
|
+
return createRunReporter({
|
|
337
|
+
sinks: [
|
|
338
|
+
createRunPresenter({
|
|
339
|
+
isTTY: process.stderr.isTTY,
|
|
340
|
+
writeDiagnostic: (text) => {
|
|
341
|
+
writer(text.endsWith("\n") ? text.slice(0, -1) : text);
|
|
342
|
+
},
|
|
343
|
+
}),
|
|
344
|
+
],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function defaultStatusWriter(): ((line: string) => void) | undefined {
|
|
349
|
+
if (!process.stderr.isTTY) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
return (line) => process.stderr.write(`${line}\n`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function describeError(error: unknown): string {
|
|
356
|
+
if (error instanceof Error) {
|
|
357
|
+
return error.message;
|
|
358
|
+
}
|
|
359
|
+
if (typeof error === "string") {
|
|
360
|
+
return error;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
return JSON.stringify(error);
|
|
364
|
+
} catch {
|
|
365
|
+
return String(error);
|
|
366
|
+
}
|
|
367
|
+
}
|