@bastani/atomic 0.8.28 → 0.8.29-alpha.2
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/CHANGELOG.md +30 -0
- package/dist/builtin/cursor/CHANGELOG.md +27 -0
- package/dist/builtin/cursor/LICENSE +26 -0
- package/dist/builtin/cursor/README.md +22 -0
- package/dist/builtin/cursor/index.ts +9 -0
- package/dist/builtin/cursor/package.json +46 -0
- package/dist/builtin/cursor/src/auth.ts +352 -0
- package/dist/builtin/cursor/src/catalog-cache.ts +155 -0
- package/dist/builtin/cursor/src/config.ts +123 -0
- package/dist/builtin/cursor/src/conversation-state.ts +135 -0
- package/dist/builtin/cursor/src/cursor-models-raw.json +583 -0
- package/dist/builtin/cursor/src/model-mapper.ts +270 -0
- package/dist/builtin/cursor/src/models.ts +54 -0
- package/dist/builtin/cursor/src/native-loader.ts +71 -0
- package/dist/builtin/cursor/src/proto/README.md +34 -0
- package/dist/builtin/cursor/src/proto/agent_pb.ts +15294 -0
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +717 -0
- package/dist/builtin/cursor/src/provider.ts +301 -0
- package/dist/builtin/cursor/src/stream.ts +564 -0
- package/dist/builtin/cursor/src/transport.ts +791 -0
- package/dist/builtin/intercom/CHANGELOG.md +4 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/intercom/skills/intercom/SKILL.md +5 -5
- package/dist/builtin/mcp/CHANGELOG.md +4 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +12 -0
- package/dist/builtin/subagents/README.md +7 -3
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -24
- package/dist/builtin/subagents/agents/debugger.md +3 -5
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +2 -1
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +2 -1
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +19 -2
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +271 -10
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +12 -39
- package/dist/builtin/subagents/src/shared/types.ts +1 -0
- package/dist/builtin/subagents/src/shared/utils.ts +50 -10
- package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +77 -0
- package/dist/builtin/subagents/src/slash/slash-commands.ts +1 -55
- package/dist/builtin/web-access/CHANGELOG.md +5 -1
- package/dist/builtin/web-access/README.md +1 -1
- package/dist/builtin/web-access/github-extract.ts +1 -1
- package/dist/builtin/web-access/package.json +3 -3
- package/dist/builtin/workflows/CHANGELOG.md +18 -0
- package/dist/builtin/workflows/README.md +19 -1
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +17 -3
- package/dist/builtin/workflows/src/extension/wiring.ts +17 -1
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +34 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +13 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +86 -14
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +11 -3
- package/dist/builtin/workflows/src/shared/types.ts +8 -4
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +64 -2
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
- package/dist/builtin/workflows/src/tui/workflow-status.ts +2 -0
- package/dist/core/builtin-packages.d.ts.map +1 -1
- package/dist/core/builtin-packages.js +6 -0
- package/dist/core/builtin-packages.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +20 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +17 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +11 -9
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +55 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/project-trust.d.ts +1 -0
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +3 -3
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/resource-loader.d.ts +9 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +72 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/structured-output.d.ts +39 -0
- package/dist/core/tools/structured-output.d.ts.map +1 -0
- package/dist/core/tools/structured-output.js +141 -0
- package/dist/core/tools/structured-output.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +36 -14
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +3 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +16 -0
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +11 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +158 -11
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +39 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/custom-provider.md +1 -0
- package/docs/extensions.md +2 -2
- package/docs/models.md +2 -0
- package/docs/packages.md +3 -1
- package/docs/providers.md +15 -0
- package/docs/sdk.md +61 -0
- package/docs/security.md +1 -1
- package/docs/subagents.md +21 -0
- package/docs/usage.md +2 -0
- package/docs/workflows.md +10 -7
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/structured-output.ts +22 -53
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +12 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { APP_NAME } from "@bastani/atomic";
|
|
4
|
+
import { APP_NAME, STRUCTURED_OUTPUT_TOOL_NAME, getStructuredOutputMetadataPath } from "@bastani/atomic";
|
|
5
5
|
import { Compile } from "typebox/compile";
|
|
6
6
|
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
7
7
|
|
|
@@ -9,10 +9,43 @@ const ENV_PREFIX = APP_NAME.toUpperCase();
|
|
|
9
9
|
export const STRUCTURED_OUTPUT_SCHEMA_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA`;
|
|
10
10
|
export const STRUCTURED_OUTPUT_CAPTURE_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE`;
|
|
11
11
|
|
|
12
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
13
|
+
type JsonArray = readonly JsonValue[];
|
|
14
|
+
type JsonRecord = { readonly [key: string]: JsonValue };
|
|
15
|
+
type JsonValue = JsonPrimitive | JsonArray | JsonRecord;
|
|
16
|
+
|
|
12
17
|
export interface StructuredOutputRuntime {
|
|
13
18
|
schema: JsonSchemaObject;
|
|
14
19
|
schemaPath: string;
|
|
15
20
|
outputPath: string;
|
|
21
|
+
metadataPath: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StructuredOutputCaptureMetadata {
|
|
25
|
+
toolName: string;
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
success: true;
|
|
28
|
+
terminate: true;
|
|
29
|
+
capturedAt?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StructuredOutputTranscriptContent {
|
|
33
|
+
readonly type?: string;
|
|
34
|
+
readonly id?: string;
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface StructuredOutputTranscriptMessage {
|
|
39
|
+
readonly role: string;
|
|
40
|
+
readonly content?: string | readonly StructuredOutputTranscriptContent[];
|
|
41
|
+
readonly toolCallId?: string;
|
|
42
|
+
readonly toolName?: string;
|
|
43
|
+
readonly isError?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ReadStructuredOutputOptions {
|
|
47
|
+
messages?: readonly StructuredOutputTranscriptMessage[];
|
|
48
|
+
toolName?: string;
|
|
16
49
|
}
|
|
17
50
|
|
|
18
51
|
interface CompiledJsonSchema {
|
|
@@ -20,21 +53,223 @@ interface CompiledJsonSchema {
|
|
|
20
53
|
Errors(value: unknown): Iterable<{ instancePath?: string; message?: string }>;
|
|
21
54
|
}
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
type JsonSchemaRootDescriptor = {
|
|
57
|
+
readonly type?: string | readonly string[];
|
|
58
|
+
readonly anyOf?: readonly JsonSchemaObject[];
|
|
59
|
+
readonly oneOf?: readonly JsonSchemaObject[];
|
|
60
|
+
readonly allOf?: readonly JsonSchemaObject[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type ToolCallBlock = {
|
|
64
|
+
readonly type: "toolCall";
|
|
65
|
+
readonly id?: string;
|
|
66
|
+
readonly name?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function schemaTypeIsObjectOnly(type: JsonSchemaRootDescriptor["type"]): boolean {
|
|
70
|
+
if (type === "object") return true;
|
|
71
|
+
return Array.isArray(type) && type.length === 1 && type[0] === "object";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isTopLevelObjectOutputSchema(schema: JsonSchemaObject): boolean {
|
|
75
|
+
const descriptor = schema as JsonSchemaRootDescriptor;
|
|
76
|
+
if (schemaTypeIsObjectOnly(descriptor.type)) return true;
|
|
77
|
+
if (descriptor.type !== undefined) return false;
|
|
78
|
+
if (Array.isArray(descriptor.anyOf) || Array.isArray(descriptor.oneOf)) return false;
|
|
79
|
+
if (Array.isArray(descriptor.allOf)) {
|
|
80
|
+
return descriptor.allOf.length > 0 && descriptor.allOf.every((member) => isTopLevelObjectOutputSchema(member));
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isJsonRecord(value: JsonValue): value is JsonRecord {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stringField(record: JsonRecord, key: string): string | undefined {
|
|
90
|
+
const value = record[key];
|
|
91
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function booleanField(record: JsonRecord, key: string): boolean | undefined {
|
|
95
|
+
const value = record[key];
|
|
96
|
+
return typeof value === "boolean" ? value : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function roleOf(message: StructuredOutputTranscriptMessage): string {
|
|
100
|
+
return message.role;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toolCallBlocks(message: StructuredOutputTranscriptMessage): ToolCallBlock[] {
|
|
104
|
+
if (roleOf(message) !== "assistant" || !Array.isArray(message.content)) return [];
|
|
105
|
+
return message.content
|
|
106
|
+
.filter((block): block is ToolCallBlock => block.type === "toolCall")
|
|
107
|
+
.map((block) => ({ type: "toolCall", id: block.id, name: block.name }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isFinalityRelevantMessage(message: StructuredOutputTranscriptMessage): boolean {
|
|
111
|
+
const role = roleOf(message);
|
|
112
|
+
// `custom` entries are host/runtime annotations (for example display/status
|
|
113
|
+
// messages) rather than additional child model output, so they should not make
|
|
114
|
+
// an otherwise-final structured_output capture look stale.
|
|
115
|
+
return role === "assistant" || role === "toolResult";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function finalityInvalid(message: string): { status: "invalid"; message: string } {
|
|
119
|
+
return { status: "invalid", message };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseCaptureMetadata(value: JsonValue): { metadata?: StructuredOutputCaptureMetadata; error?: string } {
|
|
123
|
+
if (!isJsonRecord(value)) {
|
|
124
|
+
return { error: "Structured output metadata sidecar must contain an object with call metadata." };
|
|
125
|
+
}
|
|
126
|
+
const toolName = stringField(value, "toolName");
|
|
127
|
+
const toolCallId = stringField(value, "toolCallId");
|
|
128
|
+
if (!toolName || !toolCallId) {
|
|
129
|
+
return { error: "Structured output metadata sidecar is missing toolName or toolCallId metadata." };
|
|
130
|
+
}
|
|
131
|
+
if (booleanField(value, "success") !== true) {
|
|
132
|
+
return { error: "Structured output capture was not marked successful." };
|
|
133
|
+
}
|
|
134
|
+
if (booleanField(value, "terminate") !== true) {
|
|
135
|
+
return { error: "Structured output capture was not marked as a terminating action." };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
metadata: {
|
|
139
|
+
toolName,
|
|
140
|
+
toolCallId,
|
|
141
|
+
success: true,
|
|
142
|
+
terminate: true,
|
|
143
|
+
...(typeof value.capturedAt === "string" ? { capturedAt: value.capturedAt } : {}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function verifyStructuredOutputFinality(
|
|
149
|
+
messages: readonly StructuredOutputTranscriptMessage[],
|
|
150
|
+
metadata: StructuredOutputCaptureMetadata,
|
|
151
|
+
expectedToolName: string,
|
|
152
|
+
): { status: "valid" } | { status: "invalid"; message: string } {
|
|
153
|
+
if (metadata.toolName !== expectedToolName) {
|
|
154
|
+
return finalityInvalid(
|
|
155
|
+
`Captured structured output tool name ${JSON.stringify(metadata.toolName)} did not match expected ${JSON.stringify(expectedToolName)}.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (messages.length === 0) {
|
|
159
|
+
return finalityInvalid("Structured output finality could not be verified because the child transcript is empty.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let structuredOutputCallCount = 0;
|
|
163
|
+
let assistantIndex = -1;
|
|
164
|
+
let matchingAssistantToolCalls: ToolCallBlock[] = [];
|
|
165
|
+
for (let index = 0; index < messages.length; index++) {
|
|
166
|
+
const calls = toolCallBlocks(messages[index]);
|
|
167
|
+
if (calls.length === 0) continue;
|
|
168
|
+
for (const call of calls) {
|
|
169
|
+
if (call.name === metadata.toolName) {
|
|
170
|
+
structuredOutputCallCount += 1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const idMatch = calls.find((call) => call.id === metadata.toolCallId);
|
|
174
|
+
if (!idMatch) continue;
|
|
175
|
+
if (idMatch.name !== metadata.toolName) {
|
|
176
|
+
return finalityInvalid(
|
|
177
|
+
`Captured structured output tool call ${JSON.stringify(metadata.toolCallId)} used tool name ${JSON.stringify(idMatch.name)} instead of ${JSON.stringify(metadata.toolName)}.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
assistantIndex = index;
|
|
181
|
+
matchingAssistantToolCalls = calls;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (structuredOutputCallCount > 1) {
|
|
185
|
+
return finalityInvalid(
|
|
186
|
+
`Captured structured output call ${JSON.stringify(metadata.toolCallId)} was not exactly once; another ${metadata.toolName} tool call appeared in the child transcript.`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (assistantIndex === -1) {
|
|
190
|
+
return finalityInvalid(
|
|
191
|
+
`No assistant tool call matched captured structured output toolCallId ${JSON.stringify(metadata.toolCallId)}.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (matchingAssistantToolCalls.length !== 1) {
|
|
195
|
+
return finalityInvalid(
|
|
196
|
+
`Captured structured output call ${JSON.stringify(metadata.toolCallId)} was accompanied by sibling tool calls in the same assistant message.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let resultIndex = -1;
|
|
201
|
+
let resultMessage: StructuredOutputTranscriptMessage | undefined;
|
|
202
|
+
for (let index = assistantIndex + 1; index < messages.length; index++) {
|
|
203
|
+
const message = messages[index];
|
|
204
|
+
if (roleOf(message) !== "toolResult") continue;
|
|
205
|
+
if (message.toolCallId !== metadata.toolCallId) continue;
|
|
206
|
+
resultIndex = index;
|
|
207
|
+
resultMessage = message;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!resultMessage) {
|
|
212
|
+
return finalityInvalid(
|
|
213
|
+
`No tool result matched captured structured output toolCallId ${JSON.stringify(metadata.toolCallId)}.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (resultMessage.toolName !== metadata.toolName) {
|
|
217
|
+
return finalityInvalid(
|
|
218
|
+
`Structured output tool result for ${JSON.stringify(metadata.toolCallId)} used tool name ${JSON.stringify(resultMessage.toolName)} instead of ${JSON.stringify(metadata.toolName)}.`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (resultMessage.isError !== false) {
|
|
222
|
+
return finalityInvalid(
|
|
223
|
+
`Structured output tool result for ${JSON.stringify(metadata.toolCallId)} was an error or did not prove success.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (let index = assistantIndex + 1; index < resultIndex; index++) {
|
|
228
|
+
const message = messages[index];
|
|
229
|
+
if (isFinalityRelevantMessage(message)) {
|
|
230
|
+
return finalityInvalid(
|
|
231
|
+
`Structured output call ${JSON.stringify(metadata.toolCallId)} was not final; another ${roleOf(message)} message appeared before its matching tool result.`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (let index = resultIndex + 1; index < messages.length; index++) {
|
|
236
|
+
const message = messages[index];
|
|
237
|
+
if (isFinalityRelevantMessage(message)) {
|
|
238
|
+
return finalityInvalid(
|
|
239
|
+
`Structured output call ${JSON.stringify(metadata.toolCallId)} was not final; a later ${roleOf(message)} message followed the successful tool result.`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { status: "valid" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function assertJsonSchemaDescriptor(schema: unknown, label = "outputSchema"): asserts schema is JsonSchemaObject {
|
|
24
248
|
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
25
|
-
throw new Error(`${label} must be a JSON Schema object.`);
|
|
249
|
+
throw new Error(`${label} must be a JSON Schema object descriptor.`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function assertStructuredOutputParameterSchema(schema: unknown, label = "outputSchema"): asserts schema is JsonSchemaObject {
|
|
254
|
+
assertJsonSchemaDescriptor(schema, label);
|
|
255
|
+
if (!isTopLevelObjectOutputSchema(schema)) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`${label} must be a top-level object tool-argument schema. `
|
|
258
|
+
+ "Wrap array or primitive outputs in an object field, for example `{ items: [...] }` or `{ value: ... }`.",
|
|
259
|
+
);
|
|
26
260
|
}
|
|
27
261
|
}
|
|
28
262
|
|
|
29
263
|
export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string): StructuredOutputRuntime {
|
|
30
|
-
|
|
264
|
+
assertStructuredOutputParameterSchema(schema);
|
|
31
265
|
const rootDir = baseDir ?? os.tmpdir();
|
|
32
266
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
33
267
|
const dir = fs.mkdtempSync(path.join(rootDir, "pi-subagent-structured-"));
|
|
34
268
|
const schemaPath = path.join(dir, "schema.json");
|
|
35
269
|
const outputPath = path.join(dir, "output.json");
|
|
270
|
+
const metadataPath = path.join(dir, "output.meta.json");
|
|
36
271
|
fs.writeFileSync(schemaPath, JSON.stringify(schema), { mode: 0o600 });
|
|
37
|
-
return { schema, schemaPath, outputPath };
|
|
272
|
+
return { schema, schemaPath, outputPath, metadataPath };
|
|
38
273
|
}
|
|
39
274
|
|
|
40
275
|
export function validateStructuredOutputValue(schema: JsonSchemaObject, value: unknown): { status: "valid" } | { status: "invalid"; message: string } {
|
|
@@ -54,19 +289,45 @@ export function validateStructuredOutputValue(schema: JsonSchemaObject, value: u
|
|
|
54
289
|
return { status: "invalid", message: errors.join("; ") || "schema validation failed" };
|
|
55
290
|
}
|
|
56
291
|
|
|
57
|
-
export function readStructuredOutput(
|
|
292
|
+
export function readStructuredOutput(
|
|
293
|
+
runtime: StructuredOutputRuntime,
|
|
294
|
+
options: ReadStructuredOutputOptions = {},
|
|
295
|
+
): { value?: unknown; error?: string } {
|
|
58
296
|
if (!fs.existsSync(runtime.outputPath)) {
|
|
59
297
|
return { error: "Missing structured_output call; this step has outputSchema and must finish by calling structured_output." };
|
|
60
298
|
}
|
|
61
|
-
|
|
299
|
+
const metadataPath = runtime.metadataPath ?? getStructuredOutputMetadataPath(runtime.outputPath);
|
|
300
|
+
if (!fs.existsSync(metadataPath)) {
|
|
301
|
+
return { error: "Missing structured_output metadata sidecar; this step must finish with a verified structured_output call." };
|
|
302
|
+
}
|
|
303
|
+
let payload: JsonValue;
|
|
62
304
|
try {
|
|
63
|
-
|
|
305
|
+
payload = JSON.parse(fs.readFileSync(runtime.outputPath, "utf-8")) as JsonValue;
|
|
64
306
|
} catch (error) {
|
|
65
307
|
return { error: `Failed to read structured output: ${error instanceof Error ? error.message : String(error)}` };
|
|
66
308
|
}
|
|
67
|
-
|
|
309
|
+
let rawMetadata: JsonValue;
|
|
310
|
+
try {
|
|
311
|
+
rawMetadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8")) as JsonValue;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return { error: `Failed to read structured output metadata: ${error instanceof Error ? error.message : String(error)}` };
|
|
314
|
+
}
|
|
315
|
+
const parsed = parseCaptureMetadata(rawMetadata);
|
|
316
|
+
if (parsed.error || !parsed.metadata) {
|
|
317
|
+
return { error: parsed.error ?? "Structured output metadata sidecar is invalid." };
|
|
318
|
+
}
|
|
319
|
+
const validation = validateStructuredOutputValue(runtime.schema, payload);
|
|
68
320
|
if (validation.status === "invalid") return { error: `Structured output validation failed: ${validation.message}` };
|
|
69
|
-
|
|
321
|
+
const expectedToolName = options.toolName ?? STRUCTURED_OUTPUT_TOOL_NAME;
|
|
322
|
+
if (options.messages) {
|
|
323
|
+
const finality = verifyStructuredOutputFinality(options.messages, parsed.metadata, expectedToolName);
|
|
324
|
+
if (finality.status === "invalid") return { error: finality.message };
|
|
325
|
+
} else if (parsed.metadata.toolName !== expectedToolName) {
|
|
326
|
+
return {
|
|
327
|
+
error: `Captured structured output tool name ${JSON.stringify(parsed.metadata.toolName)} did not match expected ${JSON.stringify(expectedToolName)}.`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { value: payload };
|
|
70
331
|
}
|
|
71
332
|
|
|
72
333
|
export function cleanupStructuredOutputRuntime(runtime: StructuredOutputRuntime | undefined): void {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import type { ExtensionAPI } from "@bastani/atomic";
|
|
4
|
-
import { getEnvValue } from "@bastani/atomic";
|
|
2
|
+
import { createStructuredOutputTool, getEnvValue, type ExtensionAPI } from "@bastani/atomic";
|
|
5
3
|
import {
|
|
6
4
|
SUBAGENT_FANOUT_CHILD_ENV,
|
|
7
5
|
SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV,
|
|
8
6
|
SUBAGENT_INHERIT_SKILLS_ENV,
|
|
9
7
|
SUBAGENT_INTERCOM_SESSION_NAME_ENV,
|
|
10
8
|
} from "./pi-args.ts";
|
|
11
|
-
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV
|
|
9
|
+
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "./structured-output.ts";
|
|
12
10
|
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
13
11
|
|
|
14
12
|
export { SUBAGENT_INTERCOM_SESSION_NAME_ENV } from "./pi-args.ts";
|
|
@@ -16,6 +14,7 @@ export { SUBAGENT_INTERCOM_SESSION_NAME_ENV } from "./pi-args.ts";
|
|
|
16
14
|
const STRUCTURED_OUTPUT_INSTRUCTIONS = [
|
|
17
15
|
"This subagent step has a strict structured output contract.",
|
|
18
16
|
"Your final action must be to call the `structured_output` tool with JSON matching the provided schema.",
|
|
17
|
+
"Pass the schema fields directly as the tool arguments; do not wrap them in `{ value: ... }` unless the schema explicitly defines a top-level `value` field.",
|
|
19
18
|
"Do not rely on prose-only completion; if you do not call `structured_output`, the parent will fail this step.",
|
|
20
19
|
].join("\n");
|
|
21
20
|
|
|
@@ -162,44 +161,18 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
162
161
|
const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
163
162
|
if (structuredOutputPath && structuredSchemaPath) {
|
|
164
163
|
const schema = JSON.parse(fs.readFileSync(structuredSchemaPath, "utf-8")) as JsonSchemaObject;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
additionalProperties: false,
|
|
170
|
-
};
|
|
171
|
-
const registerTool = pi.registerTool as unknown as (tool: {
|
|
172
|
-
name: string;
|
|
173
|
-
label: string;
|
|
174
|
-
description: string;
|
|
175
|
-
parameters: unknown;
|
|
176
|
-
execute: (_id: string, params: { value: unknown }) => Promise<unknown>;
|
|
177
|
-
}) => void;
|
|
178
|
-
registerTool({
|
|
179
|
-
name: "structured_output",
|
|
180
|
-
label: "Structured Output",
|
|
181
|
-
description: "Submit the required final structured output for this subagent step. This terminates the step.",
|
|
182
|
-
parameters: parameters as never,
|
|
183
|
-
async execute(_id: string, params: { value: unknown }) {
|
|
184
|
-
const validation = validateStructuredOutputValue(schema, params.value);
|
|
185
|
-
if (validation.status === "invalid") {
|
|
186
|
-
throw new Error(`Structured output validation failed: ${validation.message}`);
|
|
187
|
-
}
|
|
188
|
-
fs.mkdirSync(path.dirname(structuredOutputPath), { recursive: true });
|
|
189
|
-
fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
|
|
190
|
-
return {
|
|
191
|
-
content: [{ type: "text", text: "Structured output captured." }],
|
|
192
|
-
details: { path: structuredOutputPath },
|
|
193
|
-
terminate: true,
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
});
|
|
164
|
+
pi.registerTool(createStructuredOutputTool({
|
|
165
|
+
schema,
|
|
166
|
+
output: { outputPath: structuredOutputPath },
|
|
167
|
+
}));
|
|
197
168
|
}
|
|
198
169
|
|
|
199
170
|
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown) => unknown) => void;
|
|
200
|
-
onRuntimeEvent("context", (event
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
171
|
+
onRuntimeEvent("context", (event) => {
|
|
172
|
+
const contextEvent = event as { messages?: unknown[] };
|
|
173
|
+
if (!Array.isArray(contextEvent.messages)) return undefined;
|
|
174
|
+
const messages = stripParentOnlySubagentMessages(contextEvent.messages);
|
|
175
|
+
if (messages === contextEvent.messages) return undefined;
|
|
203
176
|
return { messages };
|
|
204
177
|
});
|
|
205
178
|
|
|
@@ -122,21 +122,61 @@ export function findLatestSessionFile(sessionDir: string): string | null {
|
|
|
122
122
|
// Message Parsing Utilities
|
|
123
123
|
// ============================================================================
|
|
124
124
|
|
|
125
|
+
const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
|
|
126
|
+
|
|
127
|
+
type TextContentCandidate = {
|
|
128
|
+
type: string;
|
|
129
|
+
text?: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function getLastNonEmptyTextContent(content: readonly TextContentCandidate[]): string | undefined {
|
|
133
|
+
for (let index = content.length - 1; index >= 0; index--) {
|
|
134
|
+
const part = content[index];
|
|
135
|
+
if (part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0) {
|
|
136
|
+
return part.text;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getCombinedNonEmptyTextContent(content: readonly TextContentCandidate[]): string | undefined {
|
|
143
|
+
let text = "";
|
|
144
|
+
for (const part of content) {
|
|
145
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
146
|
+
text += part.text;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return text.trim().length > 0 ? text : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getStructuredOutputToolResultText(message: Message): string | undefined {
|
|
153
|
+
if (message.role !== "toolResult") return undefined;
|
|
154
|
+
if (message.toolName !== STRUCTURED_OUTPUT_TOOL_NAME) return undefined;
|
|
155
|
+
if (message.isError === true) return undefined;
|
|
156
|
+
return getCombinedNonEmptyTextContent(message.content);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getAssistantOutputText(message: Message): string | undefined {
|
|
160
|
+
if (message.role !== "assistant") return undefined;
|
|
161
|
+
const hasAssistantError = ("errorMessage" in message && typeof message.errorMessage === "string" && message.errorMessage.length > 0)
|
|
162
|
+
|| ("stopReason" in message && message.stopReason === "error");
|
|
163
|
+
if (hasAssistantError) return undefined;
|
|
164
|
+
return getLastNonEmptyTextContent(message.content);
|
|
165
|
+
}
|
|
166
|
+
|
|
125
167
|
/**
|
|
126
168
|
* Get the final text output from a list of messages
|
|
127
169
|
*/
|
|
128
170
|
export function getFinalOutput(messages: Message[]): string {
|
|
171
|
+
const finalMessage = messages[messages.length - 1];
|
|
172
|
+
if (finalMessage) {
|
|
173
|
+
const finalStructuredOutput = getStructuredOutputToolResultText(finalMessage);
|
|
174
|
+
if (finalStructuredOutput !== undefined) return finalStructuredOutput;
|
|
175
|
+
}
|
|
176
|
+
|
|
129
177
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
const hasAssistantError = ("errorMessage" in msg && typeof msg.errorMessage === "string" && msg.errorMessage.length > 0)
|
|
133
|
-
|| ("stopReason" in msg && msg.stopReason === "error");
|
|
134
|
-
if (hasAssistantError) continue;
|
|
135
|
-
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
136
|
-
const part = msg.content[j];
|
|
137
|
-
if (part.type === "text" && part.text.trim().length > 0) return part.text;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
178
|
+
const assistantOutput = getAssistantOutputText(messages[i]);
|
|
179
|
+
if (assistantOutput !== undefined) return assistantOutput;
|
|
140
180
|
}
|
|
141
181
|
return "";
|
|
142
182
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ChainConfig } from "../agents/agents.ts";
|
|
4
|
+
import { assertJsonSchemaDescriptor, assertStructuredOutputParameterSchema } from "../runs/shared/structured-output.ts";
|
|
5
|
+
import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
6
|
+
import type { JsonSchemaObject } from "../shared/types.ts";
|
|
7
|
+
|
|
8
|
+
function loadSavedOutputSchema(
|
|
9
|
+
chain: ChainConfig,
|
|
10
|
+
stepAgent: string,
|
|
11
|
+
outputSchema: unknown,
|
|
12
|
+
options: { schemaRole: "tool-parameters" | "collection" } = { schemaRole: "tool-parameters" },
|
|
13
|
+
): JsonSchemaObject | undefined {
|
|
14
|
+
if (outputSchema === undefined) return undefined;
|
|
15
|
+
const labelForSchema = (schemaPath?: string): string => schemaPath
|
|
16
|
+
? `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`
|
|
17
|
+
: `outputSchema for chain '${chain.name}' step '${stepAgent}'`;
|
|
18
|
+
const validateSavedSchema = (schema: unknown, label: string): JsonSchemaObject => {
|
|
19
|
+
if (options.schemaRole === "collection") {
|
|
20
|
+
assertJsonSchemaDescriptor(schema, label);
|
|
21
|
+
} else {
|
|
22
|
+
assertStructuredOutputParameterSchema(schema, label);
|
|
23
|
+
}
|
|
24
|
+
return schema;
|
|
25
|
+
};
|
|
26
|
+
if (typeof outputSchema === "string") {
|
|
27
|
+
const schemaPath = path.isAbsolute(outputSchema)
|
|
28
|
+
? outputSchema
|
|
29
|
+
: path.join(path.dirname(chain.filePath), outputSchema);
|
|
30
|
+
const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as unknown;
|
|
31
|
+
return validateSavedSchema(parsed, labelForSchema(schemaPath));
|
|
32
|
+
}
|
|
33
|
+
return validateSavedSchema(outputSchema, labelForSchema());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function mapSavedChainSteps(chain: ChainConfig, worktree = false): ChainStep[] {
|
|
37
|
+
return (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {
|
|
38
|
+
if (isParallelStep(step)) {
|
|
39
|
+
const parallel = step.parallel.map((task) => {
|
|
40
|
+
const { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };
|
|
41
|
+
const outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);
|
|
42
|
+
return { ...rest, ...(outputSchema ? { outputSchema } : {}) };
|
|
43
|
+
});
|
|
44
|
+
return { ...step, parallel, ...(worktree ? { worktree: true } : {}) };
|
|
45
|
+
}
|
|
46
|
+
if (isDynamicParallelStep(step)) {
|
|
47
|
+
const { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };
|
|
48
|
+
const outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);
|
|
49
|
+
const collectSchema = loadSavedOutputSchema(
|
|
50
|
+
chain,
|
|
51
|
+
`${step.collect.as} collection`,
|
|
52
|
+
step.collect.outputSchema,
|
|
53
|
+
{ schemaRole: "collection" },
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
...step,
|
|
57
|
+
parallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },
|
|
58
|
+
collect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);
|
|
62
|
+
return {
|
|
63
|
+
agent: step.agent,
|
|
64
|
+
task: step.task || undefined,
|
|
65
|
+
...(step.phase ? { phase: step.phase } : {}),
|
|
66
|
+
...(step.label ? { label: step.label } : {}),
|
|
67
|
+
...(step.as ? { as: step.as } : {}),
|
|
68
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
69
|
+
output: step.output,
|
|
70
|
+
outputMode: step.outputMode,
|
|
71
|
+
reads: step.reads,
|
|
72
|
+
progress: step.progress,
|
|
73
|
+
skill: step.skill ?? step.skills,
|
|
74
|
+
model: step.model,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -5,8 +5,7 @@ import type { ExtensionAPI, ExtensionContext } from "@bastani/atomic";
|
|
|
5
5
|
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
6
6
|
import { discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
|
|
7
7
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
8
|
-
import {
|
|
9
|
-
import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
|
|
8
|
+
import { mapSavedChainSteps } from "./saved-chain-mapping.ts";
|
|
10
9
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
11
10
|
import {
|
|
12
11
|
applySlashUpdate,
|
|
@@ -21,7 +20,6 @@ import {
|
|
|
21
20
|
SLASH_SUBAGENT_RESPONSE_EVENT,
|
|
22
21
|
SLASH_SUBAGENT_STARTED_EVENT,
|
|
23
22
|
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
24
|
-
type JsonSchemaObject,
|
|
25
23
|
type SingleResult,
|
|
26
24
|
type SubagentState,
|
|
27
25
|
} from "../shared/types.ts";
|
|
@@ -125,58 +123,6 @@ const makeChainCompletions = (state: SubagentState) => (prefix: string) => {
|
|
|
125
123
|
.map((chain) => ({ value: chain.name, label: chain.name }));
|
|
126
124
|
};
|
|
127
125
|
|
|
128
|
-
function loadSavedOutputSchema(chain: ChainConfig, stepAgent: string, outputSchema: unknown): JsonSchemaObject | undefined {
|
|
129
|
-
if (outputSchema === undefined) return undefined;
|
|
130
|
-
if (typeof outputSchema === "string") {
|
|
131
|
-
const schemaPath = path.isAbsolute(outputSchema)
|
|
132
|
-
? outputSchema
|
|
133
|
-
: path.join(path.dirname(chain.filePath), outputSchema);
|
|
134
|
-
const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as unknown;
|
|
135
|
-
assertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);
|
|
136
|
-
return parsed;
|
|
137
|
-
}
|
|
138
|
-
assertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);
|
|
139
|
-
return outputSchema;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const mapSavedChainSteps = (chain: ChainConfig, worktree = false): ChainStep[] => {
|
|
143
|
-
return (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {
|
|
144
|
-
if (isParallelStep(step)) {
|
|
145
|
-
const parallel = step.parallel.map((task) => {
|
|
146
|
-
const { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };
|
|
147
|
-
const outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);
|
|
148
|
-
return { ...rest, ...(outputSchema ? { outputSchema } : {}) };
|
|
149
|
-
});
|
|
150
|
-
return { ...step, parallel, ...(worktree ? { worktree: true } : {}) };
|
|
151
|
-
}
|
|
152
|
-
if (isDynamicParallelStep(step)) {
|
|
153
|
-
const { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };
|
|
154
|
-
const outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);
|
|
155
|
-
const collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);
|
|
156
|
-
return {
|
|
157
|
-
...step,
|
|
158
|
-
parallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },
|
|
159
|
-
collect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
const outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);
|
|
163
|
-
return {
|
|
164
|
-
agent: step.agent,
|
|
165
|
-
task: step.task || undefined,
|
|
166
|
-
...(step.phase ? { phase: step.phase } : {}),
|
|
167
|
-
...(step.label ? { label: step.label } : {}),
|
|
168
|
-
...(step.as ? { as: step.as } : {}),
|
|
169
|
-
...(outputSchema ? { outputSchema } : {}),
|
|
170
|
-
output: step.output,
|
|
171
|
-
outputMode: step.outputMode,
|
|
172
|
-
reads: step.reads,
|
|
173
|
-
progress: step.progress,
|
|
174
|
-
skill: step.skill ?? step.skills,
|
|
175
|
-
model: step.model,
|
|
176
|
-
};
|
|
177
|
-
});
|
|
178
|
-
};
|
|
179
|
-
|
|
180
126
|
async function requestSlashRun(
|
|
181
127
|
pi: ExtensionAPI,
|
|
182
128
|
ctx: ExtensionContext,
|
|
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Published a synchronized Atomic 0.8.29-alpha.1 prerelease with the upstream pi TUI dependency aligned to `^0.79.3`; no functional changes were made in the web-access extension.
|
|
10
|
+
|
|
7
11
|
## [0.8.28] - 2026-06-11
|
|
8
12
|
|
|
9
13
|
### Changed
|
|
@@ -434,7 +438,7 @@ All notable changes to this project will be documented in this file.
|
|
|
434
438
|
## [0.5.0] - 2026-02-01
|
|
435
439
|
|
|
436
440
|
### Added
|
|
437
|
-
- GitHub repository clone extraction for `fetch_content` -- detects GitHub code URLs, clones repos to `/tmp/
|
|
441
|
+
- GitHub repository clone extraction for `fetch_content` -- detects GitHub code URLs, clones repos to `/tmp/atomic-github-repos/`, and returns actual file contents plus local path for further exploration with `read` and `bash`
|
|
438
442
|
- Lightweight API fallback for oversized repos (>350MB) and commit SHA URLs via `gh api`
|
|
439
443
|
- Clone cache with concurrent request deduplication (second request awaits first's clone)
|
|
440
444
|
- `forceClone` parameter on `fetch_content` to override the size threshold
|
|
@@ -265,7 +265,7 @@ All config lives in `~/.pi/web-search.json`. Every field is optional.
|
|
|
265
265
|
"enabled": true,
|
|
266
266
|
"maxRepoSizeMB": 350,
|
|
267
267
|
"cloneTimeoutSeconds": 30,
|
|
268
|
-
"clonePath": "/tmp/
|
|
268
|
+
"clonePath": "/tmp/atomic-github-repos"
|
|
269
269
|
},
|
|
270
270
|
"youtube": {
|
|
271
271
|
"enabled": true,
|