@looplia/looplia-cli 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OIE7CSW6.js → chunk-PXCY2LDE.js} +731 -80
- package/dist/{chunk-HMJDIX7C.js → chunk-QQGRKUSM.js} +9 -1
- package/dist/{chunk-QLL3W74D.js → chunk-VUASEQOQ.js} +27 -2
- package/dist/{chunk-2ETB5RP7.js → chunk-XKLZXCWO.js} +67 -4
- package/dist/{claude-agent-sdk-YGNDVP55.js → claude-agent-sdk-IC25DTKL.js} +3 -1
- package/dist/cli.js +119 -34
- package/dist/{dist-3B4KUA76.js → dist-LKL7WJ7K.js} +3 -3
- package/dist/{sandbox-LD3EU4YO.js → sandbox-XVMNWAJO.js} +5 -3
- package/dist/{sync-MFM46YAB-SSBMNJ7D.js → sync-E5PGFGNI-IGGJR7IL.js} +2 -2
- package/package.json +1 -1
- package/plugins/looplia-core/skills/workflow-executor/SKILL.md +3 -3
- package/plugins/looplia-core/skills/workflow-schema-composer/SKILL.md +15 -9
- package/plugins/looplia-writer/workflows/writing-kit.md +1 -3
- package/plugins/looplia-core/hooks/hooks.json +0 -22
- package/plugins/looplia-core/scripts/hooks/compact-inject-state.sh +0 -36
- package/plugins/looplia-core/scripts/hooks/post-write-validate.sh +0 -81
- package/plugins/looplia-core/scripts/hooks/stop-guard.sh +0 -56
- package/plugins/looplia-core/skills/search/SKILL.md +0 -174
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
init_esm_shims
|
|
8
8
|
} from "./chunk-Y55L47HC.js";
|
|
9
9
|
|
|
10
|
-
// ../../packages/provider/dist/chunk-
|
|
10
|
+
// ../../packages/provider/dist/chunk-DRG2TRPF.js
|
|
11
11
|
init_esm_shims();
|
|
12
12
|
import { createHash } from "crypto";
|
|
13
13
|
import {
|
|
@@ -175,7 +175,7 @@ async function copyPlugins(targetDir, sourcePath) {
|
|
|
175
175
|
);
|
|
176
176
|
const { initializeRegistry, compileRegistry } = await import("./compiler-4B63UTUP-VE77VSJ2.js");
|
|
177
177
|
await initializeRegistry();
|
|
178
|
-
const { syncRegistrySources } = await import("./sync-
|
|
178
|
+
const { syncRegistrySources } = await import("./sync-E5PGFGNI-IGGJR7IL.js");
|
|
179
179
|
const syncResults = await syncRegistrySources({ showProgress: true });
|
|
180
180
|
for (const result of syncResults) {
|
|
181
181
|
if (result.status === "failed") {
|
|
@@ -293,7 +293,7 @@ async function getPluginPaths() {
|
|
|
293
293
|
return await getProdPluginPaths();
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
// ../../packages/provider/dist/chunk-
|
|
296
|
+
// ../../packages/provider/dist/chunk-KZZIM7FY.js
|
|
297
297
|
init_esm_shims();
|
|
298
298
|
import { execSync } from "child_process";
|
|
299
299
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -5592,6 +5592,9 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
5592
5592
|
return combined;
|
|
5593
5593
|
};
|
|
5594
5594
|
|
|
5595
|
+
// ../../packages/provider/dist/chunk-KZZIM7FY.js
|
|
5596
|
+
import { join as join6 } from "path";
|
|
5597
|
+
|
|
5595
5598
|
// ../../node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
5596
5599
|
init_esm_shims();
|
|
5597
5600
|
import { join as join5 } from "path";
|
|
@@ -22127,12 +22130,25 @@ function query({
|
|
|
22127
22130
|
return queryInstance;
|
|
22128
22131
|
}
|
|
22129
22132
|
|
|
22130
|
-
// ../../packages/provider/dist/chunk-
|
|
22133
|
+
// ../../packages/provider/dist/chunk-KZZIM7FY.js
|
|
22131
22134
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
22132
22135
|
import { join as join33 } from "path";
|
|
22133
|
-
import {
|
|
22136
|
+
import { access, readdir as readdir3, readFile as readFile22, stat } from "fs/promises";
|
|
22137
|
+
import { join as join42 } from "path";
|
|
22138
|
+
import { cp as cp2, mkdir as mkdir22, readFile as readFile3, rm as rm22, writeFile as writeFile22 } from "fs/promises";
|
|
22134
22139
|
import { homedir as homedir32 } from "os";
|
|
22135
|
-
import { isAbsolute, join as
|
|
22140
|
+
import { isAbsolute, join as join52, normalize, resolve } from "path";
|
|
22141
|
+
import {
|
|
22142
|
+
open as open2,
|
|
22143
|
+
readdir as readdir22,
|
|
22144
|
+
readFile as readFile4,
|
|
22145
|
+
rename as rename2,
|
|
22146
|
+
stat as stat2,
|
|
22147
|
+
unlink,
|
|
22148
|
+
writeFile as writeFile3
|
|
22149
|
+
} from "fs/promises";
|
|
22150
|
+
import { join as join7 } from "path";
|
|
22151
|
+
import { join as join8 } from "path";
|
|
22136
22152
|
var LINE_SPLIT_REGEX = /\r?\n/;
|
|
22137
22153
|
function findSdkBundledCliPath() {
|
|
22138
22154
|
try {
|
|
@@ -22226,11 +22242,42 @@ var PRESETS = {
|
|
|
22226
22242
|
ANTHROPIC_CLAUDE_SONNET: {
|
|
22227
22243
|
name: "Anthropic Claude Sonnet",
|
|
22228
22244
|
apiProvider: "anthropic",
|
|
22229
|
-
mainModel: "claude-sonnet-4-5-
|
|
22230
|
-
executorModel: "claude-sonnet-4-5-
|
|
22231
|
-
haikuModel: "claude-sonnet-4-5-
|
|
22232
|
-
sonnetModel: "claude-sonnet-4-5-
|
|
22233
|
-
opusModel: "claude-sonnet-4-5-
|
|
22245
|
+
mainModel: "claude-sonnet-4-5-20250929",
|
|
22246
|
+
executorModel: "claude-sonnet-4-5-20250929",
|
|
22247
|
+
haikuModel: "claude-sonnet-4-5-20250929",
|
|
22248
|
+
sonnetModel: "claude-sonnet-4-5-20250929",
|
|
22249
|
+
opusModel: "claude-sonnet-4-5-20250929"
|
|
22250
|
+
},
|
|
22251
|
+
// Claude Code Subscription (macOS Keychain)
|
|
22252
|
+
CLAUDE_CODE_SUBSCRIPTION_HAIKU: {
|
|
22253
|
+
name: "Claude Code Subscription (Haiku)",
|
|
22254
|
+
apiProvider: "anthropic",
|
|
22255
|
+
authTokenSource: "subscription",
|
|
22256
|
+
mainModel: "claude-haiku-4-5-20251001",
|
|
22257
|
+
executorModel: "claude-haiku-4-5-20251001",
|
|
22258
|
+
haikuModel: "claude-haiku-4-5-20251001",
|
|
22259
|
+
sonnetModel: "claude-haiku-4-5-20251001",
|
|
22260
|
+
opusModel: "claude-haiku-4-5-20251001"
|
|
22261
|
+
},
|
|
22262
|
+
CLAUDE_CODE_SUBSCRIPTION_SONNET: {
|
|
22263
|
+
name: "Claude Code Subscription (Sonnet)",
|
|
22264
|
+
apiProvider: "anthropic",
|
|
22265
|
+
authTokenSource: "subscription",
|
|
22266
|
+
mainModel: "claude-sonnet-4-5-20250929",
|
|
22267
|
+
executorModel: "claude-sonnet-4-5-20250929",
|
|
22268
|
+
haikuModel: "claude-sonnet-4-5-20250929",
|
|
22269
|
+
sonnetModel: "claude-sonnet-4-5-20250929",
|
|
22270
|
+
opusModel: "claude-sonnet-4-5-20250929"
|
|
22271
|
+
},
|
|
22272
|
+
CLAUDE_CODE_SUBSCRIPTION_OPUS: {
|
|
22273
|
+
name: "Claude Code Subscription (Opus)",
|
|
22274
|
+
apiProvider: "anthropic",
|
|
22275
|
+
authTokenSource: "subscription",
|
|
22276
|
+
mainModel: "claude-opus-4-5-20251101",
|
|
22277
|
+
executorModel: "claude-opus-4-5-20251101",
|
|
22278
|
+
haikuModel: "claude-opus-4-5-20251101",
|
|
22279
|
+
sonnetModel: "claude-opus-4-5-20251101",
|
|
22280
|
+
opusModel: "claude-opus-4-5-20251101"
|
|
22234
22281
|
},
|
|
22235
22282
|
// ZenMux Presets
|
|
22236
22283
|
ZENMUX_ANTHROPIC_HAIKU45: {
|
|
@@ -22253,6 +22300,16 @@ var PRESETS = {
|
|
|
22253
22300
|
sonnetModel: "z-ai/glm-4.7",
|
|
22254
22301
|
opusModel: "z-ai/glm-4.7"
|
|
22255
22302
|
},
|
|
22303
|
+
ZENMUX_ZAI_GLM47FLASHX: {
|
|
22304
|
+
name: "ZenMux GLM-4.7-FlashX",
|
|
22305
|
+
apiProvider: "zenmux",
|
|
22306
|
+
baseUrl: "https://zenmux.ai/api/anthropic",
|
|
22307
|
+
mainModel: "z-ai/glm-4.7-flashx",
|
|
22308
|
+
executorModel: "z-ai/glm-4.7-flashx",
|
|
22309
|
+
haikuModel: "z-ai/glm-4.7-flashx",
|
|
22310
|
+
sonnetModel: "z-ai/glm-4.7-flashx",
|
|
22311
|
+
opusModel: "z-ai/glm-4.7-flashx"
|
|
22312
|
+
},
|
|
22256
22313
|
ZENMUX_MINIMAX_M21: {
|
|
22257
22314
|
name: "ZenMux MiniMax-M2.1",
|
|
22258
22315
|
apiProvider: "zenmux",
|
|
@@ -22372,6 +22429,48 @@ var PRESETS = {
|
|
|
22372
22429
|
haikuModel: "openai/gpt-5.1-codex-mini",
|
|
22373
22430
|
sonnetModel: "openai/gpt-5.1-codex-mini",
|
|
22374
22431
|
opusModel: "openai/gpt-5.1-codex-mini"
|
|
22432
|
+
},
|
|
22433
|
+
// OpenRouter Presets
|
|
22434
|
+
OPENROUTER_PRESET: {
|
|
22435
|
+
name: "OpenRouter (User-Configured Preset)",
|
|
22436
|
+
apiProvider: "openrouter",
|
|
22437
|
+
baseUrl: "https://openrouter.ai/api",
|
|
22438
|
+
mainModel: "@preset/looplia-default",
|
|
22439
|
+
executorModel: "@preset/looplia-default",
|
|
22440
|
+
haikuModel: "@preset/looplia-default",
|
|
22441
|
+
sonnetModel: "@preset/looplia-default",
|
|
22442
|
+
opusModel: "@preset/looplia-default"
|
|
22443
|
+
},
|
|
22444
|
+
OPENROUTER_ZAI_GLM47FLASH: {
|
|
22445
|
+
name: "OpenRouter GLM-4.7-Flash",
|
|
22446
|
+
apiProvider: "openrouter",
|
|
22447
|
+
baseUrl: "https://openrouter.ai/api",
|
|
22448
|
+
mainModel: "z-ai/glm-4.7-flash",
|
|
22449
|
+
executorModel: "z-ai/glm-4.7-flash",
|
|
22450
|
+
haikuModel: "z-ai/glm-4.7-flash",
|
|
22451
|
+
sonnetModel: "z-ai/glm-4.7-flash",
|
|
22452
|
+
opusModel: "z-ai/glm-4.7-flash"
|
|
22453
|
+
},
|
|
22454
|
+
// Ollama Cloud Presets
|
|
22455
|
+
OLLAMA_GLM47_CLOUD: {
|
|
22456
|
+
name: "Ollama GLM-4.7 Cloud",
|
|
22457
|
+
apiProvider: "ollama",
|
|
22458
|
+
baseUrl: "http://localhost:11434",
|
|
22459
|
+
mainModel: "glm-4.7:cloud",
|
|
22460
|
+
executorModel: "glm-4.7:cloud",
|
|
22461
|
+
haikuModel: "glm-4.7:cloud",
|
|
22462
|
+
sonnetModel: "glm-4.7:cloud",
|
|
22463
|
+
opusModel: "glm-4.7:cloud"
|
|
22464
|
+
},
|
|
22465
|
+
OLLAMA_MINIMAX_M21_CLOUD: {
|
|
22466
|
+
name: "Ollama MiniMax-M2.1 Cloud",
|
|
22467
|
+
apiProvider: "ollama",
|
|
22468
|
+
baseUrl: "http://localhost:11434",
|
|
22469
|
+
mainModel: "minimax-m2.1:cloud",
|
|
22470
|
+
executorModel: "minimax-m2.1:cloud",
|
|
22471
|
+
haikuModel: "minimax-m2.1:cloud",
|
|
22472
|
+
sonnetModel: "minimax-m2.1:cloud",
|
|
22473
|
+
opusModel: "minimax-m2.1:cloud"
|
|
22375
22474
|
}
|
|
22376
22475
|
};
|
|
22377
22476
|
var CONFIG_FILE = "looplia.setting.json";
|
|
@@ -22423,19 +22522,45 @@ function injectModelTierEnv(mainModel, executorModel) {
|
|
|
22423
22522
|
setEnvIfNotSet("LOOPLIA_AGENT_MODEL_MAIN", mainModel);
|
|
22424
22523
|
setEnvIfNotSet("LOOPLIA_AGENT_MODEL_EXECUTOR", executorModel);
|
|
22425
22524
|
}
|
|
22426
|
-
function
|
|
22427
|
-
if (
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22525
|
+
function injectSubscriptionAuth() {
|
|
22526
|
+
if (!process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
22527
|
+
console.warn(
|
|
22528
|
+
"Warning: CLAUDE_CODE_OAUTH_TOKEN not set. Set this environment variable to use Claude Code subscription auth."
|
|
22529
|
+
);
|
|
22530
|
+
}
|
|
22531
|
+
process.env.ANTHROPIC_API_KEY = void 0;
|
|
22532
|
+
}
|
|
22533
|
+
function injectNonAnthropicProviderEnv(apiProvider) {
|
|
22534
|
+
if (apiProvider.baseUrl && !process.env.ANTHROPIC_BASE_URL) {
|
|
22535
|
+
process.env.ANTHROPIC_BASE_URL = apiProvider.baseUrl;
|
|
22536
|
+
}
|
|
22537
|
+
const isOpenRouterEndpoint = apiProvider.type === "openrouter" || apiProvider.baseUrl?.includes("openrouter.ai");
|
|
22538
|
+
const isZenmuxEndpoint = apiProvider.type === "zenmux" || apiProvider.baseUrl?.includes("zenmux.ai");
|
|
22539
|
+
const isOllamaEndpoint = apiProvider.type === "ollama" || apiProvider.baseUrl?.includes("localhost:11434");
|
|
22540
|
+
if (apiProvider.authToken) {
|
|
22541
|
+
if (isOpenRouterEndpoint) {
|
|
22542
|
+
process.env.ANTHROPIC_AUTH_TOKEN = apiProvider.authToken;
|
|
22543
|
+
process.env.ANTHROPIC_API_KEY = void 0;
|
|
22433
22544
|
} else {
|
|
22434
|
-
|
|
22435
|
-
if (isZenmuxEndpoint && process.env.ZENMUX_API_KEY) {
|
|
22436
|
-
process.env.ANTHROPIC_API_KEY = process.env.ZENMUX_API_KEY;
|
|
22437
|
-
}
|
|
22545
|
+
process.env.ANTHROPIC_API_KEY = apiProvider.authToken;
|
|
22438
22546
|
}
|
|
22547
|
+
return;
|
|
22548
|
+
}
|
|
22549
|
+
if (isZenmuxEndpoint && process.env.ZENMUX_API_KEY) {
|
|
22550
|
+
process.env.ANTHROPIC_API_KEY = process.env.ZENMUX_API_KEY;
|
|
22551
|
+
} else if (isOpenRouterEndpoint && process.env.OPENROUTER_API_KEY) {
|
|
22552
|
+
process.env.ANTHROPIC_AUTH_TOKEN = process.env.OPENROUTER_API_KEY;
|
|
22553
|
+
process.env.ANTHROPIC_API_KEY = void 0;
|
|
22554
|
+
} else if (isOllamaEndpoint) {
|
|
22555
|
+
process.env.ANTHROPIC_API_KEY = process.env.OLLAMA_API_KEY || "ollama";
|
|
22556
|
+
}
|
|
22557
|
+
}
|
|
22558
|
+
function injectLoopliaSettingsEnv(settings) {
|
|
22559
|
+
if (settings.apiProvider.authTokenSource === "subscription") {
|
|
22560
|
+
injectSubscriptionAuth();
|
|
22561
|
+
}
|
|
22562
|
+
if (settings.apiProvider.type !== "anthropic") {
|
|
22563
|
+
injectNonAnthropicProviderEnv(settings.apiProvider);
|
|
22439
22564
|
}
|
|
22440
22565
|
injectModelTierEnv(settings.agents.main, settings.agents.executor);
|
|
22441
22566
|
}
|
|
@@ -22456,6 +22581,7 @@ function getSettingsDisplayInfo(settings) {
|
|
|
22456
22581
|
preset: settings.preset,
|
|
22457
22582
|
provider,
|
|
22458
22583
|
authToken: settings.apiProvider.authToken,
|
|
22584
|
+
authTokenSource: settings.apiProvider.authTokenSource,
|
|
22459
22585
|
agents: {
|
|
22460
22586
|
main: settings.agents.main,
|
|
22461
22587
|
executor: settings.agents.executor
|
|
@@ -22479,7 +22605,8 @@ function applyPreset(presetName, existingSettings) {
|
|
|
22479
22605
|
apiProvider: {
|
|
22480
22606
|
type: preset.apiProvider,
|
|
22481
22607
|
baseUrl: preset.baseUrl,
|
|
22482
|
-
authToken: existingSettings?.apiProvider.authToken
|
|
22608
|
+
authToken: existingSettings?.apiProvider.authToken,
|
|
22609
|
+
authTokenSource: preset.authTokenSource
|
|
22483
22610
|
},
|
|
22484
22611
|
agents: {
|
|
22485
22612
|
main: preset.mainModel,
|
|
@@ -22498,17 +22625,19 @@ async function initializeCommandEnvironment(options = {}) {
|
|
|
22498
22625
|
return { settings };
|
|
22499
22626
|
}
|
|
22500
22627
|
function validateApiKeyPresence() {
|
|
22501
|
-
if (!(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN)) {
|
|
22628
|
+
if (!(process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_CODE_OAUTH_TOKEN)) {
|
|
22502
22629
|
console.error("Error: API key required");
|
|
22503
22630
|
console.error("");
|
|
22504
22631
|
console.error("Options:");
|
|
22505
22632
|
console.error(" 1. Set ANTHROPIC_API_KEY environment variable");
|
|
22506
22633
|
console.error(" 2. Set ZENMUX_API_KEY with a ZenMux preset");
|
|
22507
|
-
console.error(" 3.
|
|
22508
|
-
console.error(" 4.
|
|
22634
|
+
console.error(" 3. Set OPENROUTER_API_KEY with an OpenRouter preset");
|
|
22635
|
+
console.error(" 4. Configure via: looplia config provider preset <name>");
|
|
22636
|
+
console.error(" 5. Use --mock flag for testing without API");
|
|
22509
22637
|
console.error("");
|
|
22510
22638
|
console.error("Get your API key from: https://console.anthropic.com");
|
|
22511
22639
|
console.error("Or use ZenMux at: https://zenmux.ai");
|
|
22640
|
+
console.error("Or use OpenRouter at: https://openrouter.ai");
|
|
22512
22641
|
process.exit(1);
|
|
22513
22642
|
}
|
|
22514
22643
|
}
|
|
@@ -22685,13 +22814,186 @@ function extractSandboxIdFromPrompt(prompt) {
|
|
|
22685
22814
|
return decodedId;
|
|
22686
22815
|
}
|
|
22687
22816
|
var extractContentIdFromPrompt = extractSandboxIdFromPrompt;
|
|
22817
|
+
var LOCK_MAX_RETRIES = 5;
|
|
22818
|
+
var LOCK_RETRY_DELAY_MS = 200;
|
|
22819
|
+
function isValidationManifest(value) {
|
|
22820
|
+
if (!value || typeof value !== "object") {
|
|
22821
|
+
return false;
|
|
22822
|
+
}
|
|
22823
|
+
const obj = value;
|
|
22824
|
+
return typeof obj.workflow === "string" && typeof obj.steps === "object" && obj.steps !== null;
|
|
22825
|
+
}
|
|
22826
|
+
async function sleep(ms) {
|
|
22827
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
22828
|
+
}
|
|
22829
|
+
async function lockExists(lockPath) {
|
|
22830
|
+
try {
|
|
22831
|
+
await access(lockPath);
|
|
22832
|
+
return true;
|
|
22833
|
+
} catch {
|
|
22834
|
+
return false;
|
|
22835
|
+
}
|
|
22836
|
+
}
|
|
22837
|
+
function validationError(field, message) {
|
|
22838
|
+
return {
|
|
22839
|
+
success: false,
|
|
22840
|
+
error: { type: "validation_error", field, message }
|
|
22841
|
+
};
|
|
22842
|
+
}
|
|
22843
|
+
async function findMostRecentSandbox(sandboxRoot) {
|
|
22844
|
+
try {
|
|
22845
|
+
const entries = await readdir3(sandboxRoot, { withFileTypes: true });
|
|
22846
|
+
const dirs = entries.filter(
|
|
22847
|
+
(e) => e.isDirectory() && !e.name.startsWith(".")
|
|
22848
|
+
);
|
|
22849
|
+
if (dirs.length === 0) {
|
|
22850
|
+
return;
|
|
22851
|
+
}
|
|
22852
|
+
const dirStats = await Promise.all(
|
|
22853
|
+
dirs.map(async (dir) => {
|
|
22854
|
+
const dirPath = join42(sandboxRoot, dir.name);
|
|
22855
|
+
const dirStat = await stat(dirPath);
|
|
22856
|
+
return { path: dirPath, mtime: dirStat.mtime.getTime() };
|
|
22857
|
+
})
|
|
22858
|
+
);
|
|
22859
|
+
dirStats.sort((a, b) => b.mtime - a.mtime);
|
|
22860
|
+
return dirStats[0]?.path;
|
|
22861
|
+
} catch {
|
|
22862
|
+
return;
|
|
22863
|
+
}
|
|
22864
|
+
}
|
|
22865
|
+
function findFinalStep(steps) {
|
|
22866
|
+
const stepEntries = Object.entries(steps);
|
|
22867
|
+
if (stepEntries.length === 0) {
|
|
22868
|
+
return;
|
|
22869
|
+
}
|
|
22870
|
+
const lastEntry = stepEntries.at(-1);
|
|
22871
|
+
if (!lastEntry) {
|
|
22872
|
+
return;
|
|
22873
|
+
}
|
|
22874
|
+
return { id: lastEntry[0], output: lastEntry[1].output };
|
|
22875
|
+
}
|
|
22876
|
+
function resolveFinalStep(steps, finalStepId) {
|
|
22877
|
+
if (finalStepId) {
|
|
22878
|
+
const step = steps[finalStepId];
|
|
22879
|
+
if (step?.output) {
|
|
22880
|
+
return { id: finalStepId, output: step.output };
|
|
22881
|
+
}
|
|
22882
|
+
}
|
|
22883
|
+
return findFinalStep(steps);
|
|
22884
|
+
}
|
|
22885
|
+
function areAllStepsValidated(steps) {
|
|
22886
|
+
const pendingSteps = [];
|
|
22887
|
+
for (const [stepId, stepState] of Object.entries(steps)) {
|
|
22888
|
+
if (!stepState.validated) {
|
|
22889
|
+
pendingSteps.push(stepId);
|
|
22890
|
+
}
|
|
22891
|
+
}
|
|
22892
|
+
return {
|
|
22893
|
+
allValidated: pendingSteps.length === 0,
|
|
22894
|
+
pendingSteps
|
|
22895
|
+
};
|
|
22896
|
+
}
|
|
22897
|
+
function resolveSandboxDir(sandboxRoot, sandboxId) {
|
|
22898
|
+
if (sandboxId) {
|
|
22899
|
+
return join42(sandboxRoot, sandboxId);
|
|
22900
|
+
}
|
|
22901
|
+
return findMostRecentSandbox(sandboxRoot);
|
|
22902
|
+
}
|
|
22903
|
+
async function readValidationManifest(sandboxDir) {
|
|
22904
|
+
const validationPath = join42(sandboxDir, "validation.json");
|
|
22905
|
+
const lockPath = `${validationPath}.lock`;
|
|
22906
|
+
for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt++) {
|
|
22907
|
+
if (await lockExists(lockPath)) {
|
|
22908
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
22909
|
+
continue;
|
|
22910
|
+
}
|
|
22911
|
+
try {
|
|
22912
|
+
const content = await readFile22(validationPath, "utf-8");
|
|
22913
|
+
const parsed = JSON.parse(content);
|
|
22914
|
+
if (!isValidationManifest(parsed)) {
|
|
22915
|
+
return { error: "Invalid validation manifest structure" };
|
|
22916
|
+
}
|
|
22917
|
+
return { manifest: parsed };
|
|
22918
|
+
} catch (error2) {
|
|
22919
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
22920
|
+
return { error: `Failed to read validation.json: ${message}` };
|
|
22921
|
+
}
|
|
22922
|
+
}
|
|
22923
|
+
return { error: "Timeout waiting for validation.json lock to release" };
|
|
22924
|
+
}
|
|
22925
|
+
async function readFinalArtifact(sandboxDir, outputPath) {
|
|
22926
|
+
const artifactPath = join42(sandboxDir, outputPath);
|
|
22927
|
+
try {
|
|
22928
|
+
const content = await readFile22(artifactPath, "utf-8");
|
|
22929
|
+
return { artifact: JSON.parse(content) };
|
|
22930
|
+
} catch (error2) {
|
|
22931
|
+
const errorMessage = `Failed to read final artifact from ${outputPath}: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22932
|
+
const isParseError = error2 instanceof Error && error2.message.includes("JSON");
|
|
22933
|
+
if (isParseError) {
|
|
22934
|
+
return {
|
|
22935
|
+
error: {
|
|
22936
|
+
success: false,
|
|
22937
|
+
error: {
|
|
22938
|
+
type: "malformed_output",
|
|
22939
|
+
expected: "valid JSON",
|
|
22940
|
+
got: "invalid JSON",
|
|
22941
|
+
message: errorMessage
|
|
22942
|
+
}
|
|
22943
|
+
}
|
|
22944
|
+
};
|
|
22945
|
+
}
|
|
22946
|
+
return { error: validationError("artifact", errorMessage) };
|
|
22947
|
+
}
|
|
22948
|
+
}
|
|
22949
|
+
async function extractSandboxResult(options) {
|
|
22950
|
+
const loopliaHome = getLoopliaPluginPath();
|
|
22951
|
+
const sandboxRoot = options?.sandboxRoot ?? join42(loopliaHome, "sandbox");
|
|
22952
|
+
const sandboxId = options?.sandboxId;
|
|
22953
|
+
const sandboxDir = await resolveSandboxDir(sandboxRoot, sandboxId);
|
|
22954
|
+
if (!sandboxDir) {
|
|
22955
|
+
const message = sandboxId ? `Sandbox not found: ${sandboxId}` : "No sandbox directories found";
|
|
22956
|
+
return validationError("sandbox", message);
|
|
22957
|
+
}
|
|
22958
|
+
const { manifest, error: readError } = await readValidationManifest(sandboxDir);
|
|
22959
|
+
if (!manifest) {
|
|
22960
|
+
return validationError("validation.json", readError ?? "Unknown error");
|
|
22961
|
+
}
|
|
22962
|
+
const { allValidated, pendingSteps } = areAllStepsValidated(manifest.steps);
|
|
22963
|
+
if (!allValidated) {
|
|
22964
|
+
return validationError(
|
|
22965
|
+
"steps",
|
|
22966
|
+
`Workflow incomplete. Pending steps: ${pendingSteps.join(", ")}`
|
|
22967
|
+
);
|
|
22968
|
+
}
|
|
22969
|
+
const finalStep = resolveFinalStep(manifest.steps, manifest.finalStepId);
|
|
22970
|
+
if (!finalStep) {
|
|
22971
|
+
return validationError("steps", "No steps found in validation manifest");
|
|
22972
|
+
}
|
|
22973
|
+
const { artifact, error: artifactError } = await readFinalArtifact(
|
|
22974
|
+
sandboxDir,
|
|
22975
|
+
finalStep.output
|
|
22976
|
+
);
|
|
22977
|
+
if (artifactError) {
|
|
22978
|
+
return artifactError;
|
|
22979
|
+
}
|
|
22980
|
+
return {
|
|
22981
|
+
success: true,
|
|
22982
|
+
data: {
|
|
22983
|
+
status: "success",
|
|
22984
|
+
sandboxId: manifest.sandboxId ?? sandboxDir.split("/").pop(),
|
|
22985
|
+
workflowId: manifest.workflow,
|
|
22986
|
+
artifact
|
|
22987
|
+
}
|
|
22988
|
+
};
|
|
22989
|
+
}
|
|
22688
22990
|
function expandPath(path) {
|
|
22689
22991
|
if (path.startsWith("~/") || path === "~") {
|
|
22690
22992
|
const home = homedir32();
|
|
22691
22993
|
if (!home) {
|
|
22692
22994
|
throw new Error("Unable to determine home directory");
|
|
22693
22995
|
}
|
|
22694
|
-
const expanded = path === "~" ? home :
|
|
22996
|
+
const expanded = path === "~" ? home : join52(home, path.slice(2));
|
|
22695
22997
|
return normalize(expanded);
|
|
22696
22998
|
}
|
|
22697
22999
|
if (isAbsolute(path)) {
|
|
@@ -22700,26 +23002,26 @@ function expandPath(path) {
|
|
|
22700
23002
|
return normalize(resolve(path));
|
|
22701
23003
|
}
|
|
22702
23004
|
function getPluginPath() {
|
|
22703
|
-
return
|
|
23005
|
+
return join52(process.cwd(), "plugins", "looplia-writer");
|
|
22704
23006
|
}
|
|
22705
23007
|
function getPluginPaths2() {
|
|
22706
|
-
const base =
|
|
23008
|
+
const base = join52(process.cwd(), "plugins");
|
|
22707
23009
|
return {
|
|
22708
|
-
core:
|
|
22709
|
-
writer:
|
|
23010
|
+
core: join52(base, "looplia-core"),
|
|
23011
|
+
writer: join52(base, "looplia-writer")
|
|
22710
23012
|
};
|
|
22711
23013
|
}
|
|
22712
23014
|
async function checkRequiredFiles(workspaceDir) {
|
|
22713
23015
|
const requiredPaths = [
|
|
22714
23016
|
// Core structure
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
|
|
23017
|
+
join52(workspaceDir, "CLAUDE.md"),
|
|
23018
|
+
join52(workspaceDir, ".claude", "agents"),
|
|
23019
|
+
join52(workspaceDir, ".claude", "skills"),
|
|
23020
|
+
join52(workspaceDir, "workflows"),
|
|
22719
23021
|
// From looplia-core plugin
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
23022
|
+
join52(workspaceDir, ".claude", "commands"),
|
|
23023
|
+
join52(workspaceDir, ".claude", "skills", "workflow-executor"),
|
|
23024
|
+
join52(workspaceDir, ".claude", "skills", "workflow-validator")
|
|
22723
23025
|
];
|
|
22724
23026
|
for (const path of requiredPaths) {
|
|
22725
23027
|
if (!await pathExists(path)) {
|
|
@@ -22748,33 +23050,33 @@ async function createTestWorkspace(workspaceDir, force) {
|
|
|
22748
23050
|
await rm22(workspaceDir, { recursive: true, force: true });
|
|
22749
23051
|
}
|
|
22750
23052
|
await mkdir22(workspaceDir, { recursive: true });
|
|
22751
|
-
await mkdir22(
|
|
22752
|
-
await mkdir22(
|
|
22753
|
-
await mkdir22(
|
|
23053
|
+
await mkdir22(join52(workspaceDir, ".claude", "agents"), { recursive: true });
|
|
23054
|
+
await mkdir22(join52(workspaceDir, ".claude", "skills"), { recursive: true });
|
|
23055
|
+
await mkdir22(join52(workspaceDir, "sandbox"), { recursive: true });
|
|
22754
23056
|
const plugins = getPluginPaths2();
|
|
22755
|
-
const writerWorkflowsDir =
|
|
23057
|
+
const writerWorkflowsDir = join52(plugins.writer, "workflows");
|
|
22756
23058
|
if (await pathExists(writerWorkflowsDir)) {
|
|
22757
|
-
await cp2(writerWorkflowsDir,
|
|
23059
|
+
await cp2(writerWorkflowsDir, join52(workspaceDir, "workflows"), {
|
|
22758
23060
|
recursive: true
|
|
22759
23061
|
});
|
|
22760
23062
|
} else {
|
|
22761
|
-
await mkdir22(
|
|
23063
|
+
await mkdir22(join52(workspaceDir, "workflows"), { recursive: true });
|
|
22762
23064
|
}
|
|
22763
|
-
const coreValidatorDir =
|
|
23065
|
+
const coreValidatorDir = join52(plugins.core, "skills", "workflow-validator");
|
|
22764
23066
|
if (await pathExists(coreValidatorDir)) {
|
|
22765
23067
|
await cp2(
|
|
22766
23068
|
coreValidatorDir,
|
|
22767
|
-
|
|
23069
|
+
join52(workspaceDir, ".claude", "skills", "workflow-validator"),
|
|
22768
23070
|
{ recursive: true }
|
|
22769
23071
|
);
|
|
22770
23072
|
}
|
|
22771
23073
|
await writeFile22(
|
|
22772
|
-
|
|
23074
|
+
join52(workspaceDir, "CLAUDE.md"),
|
|
22773
23075
|
"# Test Workspace\n",
|
|
22774
23076
|
"utf-8"
|
|
22775
23077
|
);
|
|
22776
23078
|
await writeFile22(
|
|
22777
|
-
|
|
23079
|
+
join52(workspaceDir, "user-profile.json"),
|
|
22778
23080
|
JSON.stringify(createDefaultProfile2(), null, 2),
|
|
22779
23081
|
"utf-8"
|
|
22780
23082
|
);
|
|
@@ -22785,63 +23087,63 @@ async function bootstrapFromPlugins(workspaceDir, plugins) {
|
|
|
22785
23087
|
await rm22(workspaceDir, { recursive: true, force: true });
|
|
22786
23088
|
}
|
|
22787
23089
|
await mkdir22(workspaceDir, { recursive: true });
|
|
22788
|
-
await mkdir22(
|
|
22789
|
-
await mkdir22(
|
|
22790
|
-
const coreCommandsDir =
|
|
23090
|
+
await mkdir22(join52(workspaceDir, ".claude"), { recursive: true });
|
|
23091
|
+
await mkdir22(join52(workspaceDir, "sandbox"), { recursive: true });
|
|
23092
|
+
const coreCommandsDir = join52(plugins.core, "commands");
|
|
22791
23093
|
if (await pathExists(coreCommandsDir)) {
|
|
22792
|
-
await cp2(coreCommandsDir,
|
|
23094
|
+
await cp2(coreCommandsDir, join52(workspaceDir, ".claude", "commands"), {
|
|
22793
23095
|
recursive: true
|
|
22794
23096
|
});
|
|
22795
23097
|
}
|
|
22796
|
-
const coreSkillsDir =
|
|
23098
|
+
const coreSkillsDir = join52(plugins.core, "skills");
|
|
22797
23099
|
if (await pathExists(coreSkillsDir)) {
|
|
22798
|
-
await cp2(coreSkillsDir,
|
|
23100
|
+
await cp2(coreSkillsDir, join52(workspaceDir, ".claude", "skills"), {
|
|
22799
23101
|
recursive: true
|
|
22800
23102
|
});
|
|
22801
23103
|
}
|
|
22802
|
-
const coreHooksDir =
|
|
23104
|
+
const coreHooksDir = join52(plugins.core, "hooks");
|
|
22803
23105
|
if (await pathExists(coreHooksDir)) {
|
|
22804
|
-
await cp2(coreHooksDir,
|
|
23106
|
+
await cp2(coreHooksDir, join52(workspaceDir, ".claude", "hooks"), {
|
|
22805
23107
|
recursive: true
|
|
22806
23108
|
});
|
|
22807
23109
|
}
|
|
22808
|
-
const coreAgentsDir =
|
|
23110
|
+
const coreAgentsDir = join52(plugins.core, "agents");
|
|
22809
23111
|
if (await pathExists(coreAgentsDir)) {
|
|
22810
|
-
await mkdir22(
|
|
22811
|
-
await cp2(coreAgentsDir,
|
|
23112
|
+
await mkdir22(join52(workspaceDir, ".claude", "agents"), { recursive: true });
|
|
23113
|
+
await cp2(coreAgentsDir, join52(workspaceDir, ".claude", "agents"), {
|
|
22812
23114
|
recursive: true
|
|
22813
23115
|
});
|
|
22814
23116
|
}
|
|
22815
|
-
const coreScriptsDir =
|
|
23117
|
+
const coreScriptsDir = join52(plugins.core, "scripts");
|
|
22816
23118
|
if (await pathExists(coreScriptsDir)) {
|
|
22817
|
-
await cp2(coreScriptsDir,
|
|
23119
|
+
await cp2(coreScriptsDir, join52(workspaceDir, "scripts"), {
|
|
22818
23120
|
recursive: true
|
|
22819
23121
|
});
|
|
22820
23122
|
}
|
|
22821
|
-
const coreClaudeMd =
|
|
23123
|
+
const coreClaudeMd = join52(plugins.core, "CLAUDE.md");
|
|
22822
23124
|
if (await pathExists(coreClaudeMd)) {
|
|
22823
|
-
await cp2(coreClaudeMd,
|
|
23125
|
+
await cp2(coreClaudeMd, join52(workspaceDir, "CLAUDE.md"));
|
|
22824
23126
|
}
|
|
22825
|
-
const writerAgentsDir =
|
|
23127
|
+
const writerAgentsDir = join52(plugins.writer, "agents");
|
|
22826
23128
|
if (await pathExists(writerAgentsDir)) {
|
|
22827
|
-
await cp2(writerAgentsDir,
|
|
23129
|
+
await cp2(writerAgentsDir, join52(workspaceDir, ".claude", "agents"), {
|
|
22828
23130
|
recursive: true
|
|
22829
23131
|
});
|
|
22830
23132
|
}
|
|
22831
|
-
const writerSkillsDir =
|
|
23133
|
+
const writerSkillsDir = join52(plugins.writer, "skills");
|
|
22832
23134
|
if (await pathExists(writerSkillsDir)) {
|
|
22833
|
-
await cp2(writerSkillsDir,
|
|
23135
|
+
await cp2(writerSkillsDir, join52(workspaceDir, ".claude", "skills"), {
|
|
22834
23136
|
recursive: true
|
|
22835
23137
|
});
|
|
22836
23138
|
}
|
|
22837
|
-
const writerWorkflowsDir =
|
|
23139
|
+
const writerWorkflowsDir = join52(plugins.writer, "workflows");
|
|
22838
23140
|
if (await pathExists(writerWorkflowsDir)) {
|
|
22839
|
-
await cp2(writerWorkflowsDir,
|
|
23141
|
+
await cp2(writerWorkflowsDir, join52(workspaceDir, "workflows"), {
|
|
22840
23142
|
recursive: true
|
|
22841
23143
|
});
|
|
22842
23144
|
}
|
|
22843
23145
|
await writeFile22(
|
|
22844
|
-
|
|
23146
|
+
join52(workspaceDir, "user-profile.json"),
|
|
22845
23147
|
JSON.stringify(createDefaultProfile2(), null, 2),
|
|
22846
23148
|
"utf-8"
|
|
22847
23149
|
);
|
|
@@ -22879,12 +23181,12 @@ function getWorkspacePath(baseDir) {
|
|
|
22879
23181
|
return expandPath(baseDir ?? "~/.looplia");
|
|
22880
23182
|
}
|
|
22881
23183
|
async function readUserProfile(workspaceDir) {
|
|
22882
|
-
const profilePath =
|
|
22883
|
-
const content = await
|
|
23184
|
+
const profilePath = join52(workspaceDir, "user-profile.json");
|
|
23185
|
+
const content = await readFile3(profilePath, "utf-8");
|
|
22884
23186
|
return JSON.parse(content);
|
|
22885
23187
|
}
|
|
22886
23188
|
async function writeUserProfile(workspaceDir, profile) {
|
|
22887
|
-
const profilePath =
|
|
23189
|
+
const profilePath = join52(workspaceDir, "user-profile.json");
|
|
22888
23190
|
await writeFile22(profilePath, JSON.stringify(profile, null, 2), "utf-8");
|
|
22889
23191
|
}
|
|
22890
23192
|
var workspaceCache = /* @__PURE__ */ new Map();
|
|
@@ -22902,7 +23204,7 @@ async function getOrInitWorkspace(baseDir, useFilesystemExtensions) {
|
|
|
22902
23204
|
return workspace;
|
|
22903
23205
|
}
|
|
22904
23206
|
function getApiKey(config2) {
|
|
22905
|
-
return config2?.apiKey ?? process.env.ANTHROPIC_API_KEY ?? process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
23207
|
+
return config2?.apiKey ?? process.env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
22906
23208
|
}
|
|
22907
23209
|
async function initializeAndValidateApiKey(config2) {
|
|
22908
23210
|
const settings = await readLoopliaSettings();
|
|
@@ -22912,7 +23214,7 @@ async function initializeAndValidateApiKey(config2) {
|
|
|
22912
23214
|
const apiKey = getApiKey(config2);
|
|
22913
23215
|
if (!apiKey) {
|
|
22914
23216
|
throw new Error(
|
|
22915
|
-
"API key is required. Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN environment variable"
|
|
23217
|
+
"API key is required. Set ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or CLAUDE_CODE_OAUTH_TOKEN environment variable"
|
|
22916
23218
|
);
|
|
22917
23219
|
}
|
|
22918
23220
|
return apiKey;
|
|
@@ -23392,7 +23694,7 @@ function truncate(str, maxLen) {
|
|
|
23392
23694
|
}
|
|
23393
23695
|
return `${str.slice(0, maxLen - 3)}...`;
|
|
23394
23696
|
}
|
|
23395
|
-
async function* executeAgenticQueryStreaming(prompt,
|
|
23697
|
+
async function* executeAgenticQueryStreaming(prompt, _jsonSchema, config2) {
|
|
23396
23698
|
const resolvedConfig = resolveConfig(config2);
|
|
23397
23699
|
await initializeAndValidateApiKey(config2);
|
|
23398
23700
|
const mainModel = getMainModel();
|
|
@@ -23464,8 +23766,12 @@ When processing --file arguments or user file paths, resolve them against the Us
|
|
|
23464
23766
|
"WebSearch",
|
|
23465
23767
|
"WebFetch"
|
|
23466
23768
|
],
|
|
23467
|
-
|
|
23769
|
+
// v0.7.2: Removed outputFormat to support non-Anthropic models
|
|
23770
|
+
// Final results are now extracted from sandbox output files via extractSandboxResult()
|
|
23468
23771
|
// v0.6.9: No custom agents - using built-in general-purpose for workflow steps
|
|
23772
|
+
// v0.7.4: Pass runHooks from config if provided (workflow protection)
|
|
23773
|
+
// Only the run command passes hooks - other SDK usage doesn't need them
|
|
23774
|
+
...config2?.runHooks && { hooks: config2.runHooks }
|
|
23469
23775
|
}
|
|
23470
23776
|
});
|
|
23471
23777
|
let finalResult;
|
|
@@ -23489,6 +23795,12 @@ When processing --file arguments or user file paths, resolve them against the Us
|
|
|
23489
23795
|
}
|
|
23490
23796
|
logger.close();
|
|
23491
23797
|
yield progressTracker.onComplete();
|
|
23798
|
+
if (!finalResult) {
|
|
23799
|
+
finalResult = await extractSandboxResult({
|
|
23800
|
+
sandboxId: process.env.LOOPLIA_SANDBOX_ID,
|
|
23801
|
+
sandboxRoot: process.env.LOOPLIA_SANDBOX_ROOT ?? join6(workspace, "sandbox")
|
|
23802
|
+
});
|
|
23803
|
+
}
|
|
23492
23804
|
return finalResult ?? {
|
|
23493
23805
|
success: false,
|
|
23494
23806
|
error: { type: "unknown", message: "No result received" }
|
|
@@ -23633,6 +23945,337 @@ function createClaudeAgentExecutor(config2) {
|
|
|
23633
23945
|
}
|
|
23634
23946
|
};
|
|
23635
23947
|
}
|
|
23948
|
+
var JSON_EXTENSION_REGEX = /\.json$/;
|
|
23949
|
+
var LOCK_TIMEOUT_MS = 3e4;
|
|
23950
|
+
var LOCK_RETRY_DELAY_MS2 = 200;
|
|
23951
|
+
async function findMostRecentSandbox2(sandboxRoot) {
|
|
23952
|
+
try {
|
|
23953
|
+
const entries = await readdir22(sandboxRoot, { withFileTypes: true });
|
|
23954
|
+
const dirs = entries.filter(
|
|
23955
|
+
(e) => e.isDirectory() && !e.name.startsWith(".")
|
|
23956
|
+
);
|
|
23957
|
+
if (dirs.length === 0) {
|
|
23958
|
+
return;
|
|
23959
|
+
}
|
|
23960
|
+
const dirStats = await Promise.all(
|
|
23961
|
+
dirs.map(async (dir) => {
|
|
23962
|
+
const dirPath = join7(sandboxRoot, dir.name);
|
|
23963
|
+
const dirStat = await stat2(dirPath);
|
|
23964
|
+
return { path: dirPath, mtime: dirStat.mtime.getTime() };
|
|
23965
|
+
})
|
|
23966
|
+
);
|
|
23967
|
+
dirStats.sort((a, b) => b.mtime - a.mtime);
|
|
23968
|
+
return dirStats[0]?.path;
|
|
23969
|
+
} catch {
|
|
23970
|
+
return;
|
|
23971
|
+
}
|
|
23972
|
+
}
|
|
23973
|
+
async function readValidationManifest2(sandboxDir) {
|
|
23974
|
+
const validationPath = join7(sandboxDir, "validation.json");
|
|
23975
|
+
try {
|
|
23976
|
+
const content = await readFile4(validationPath, "utf-8");
|
|
23977
|
+
return JSON.parse(content);
|
|
23978
|
+
} catch {
|
|
23979
|
+
return;
|
|
23980
|
+
}
|
|
23981
|
+
}
|
|
23982
|
+
function resolveSandboxRoot(context) {
|
|
23983
|
+
return context?.sandboxRoot ?? join7(getLoopliaPluginPath(), "sandbox");
|
|
23984
|
+
}
|
|
23985
|
+
async function resolveSandboxDir2(context) {
|
|
23986
|
+
const sandboxRoot = resolveSandboxRoot(context);
|
|
23987
|
+
if (context?.sandboxId) {
|
|
23988
|
+
return join7(sandboxRoot, context.sandboxId);
|
|
23989
|
+
}
|
|
23990
|
+
return await findMostRecentSandbox2(sandboxRoot);
|
|
23991
|
+
}
|
|
23992
|
+
function isRecord(value) {
|
|
23993
|
+
return typeof value === "object" && value !== null;
|
|
23994
|
+
}
|
|
23995
|
+
function getSandboxArtifactInfo(filePath) {
|
|
23996
|
+
if (!filePath) {
|
|
23997
|
+
return;
|
|
23998
|
+
}
|
|
23999
|
+
if (!(filePath.includes("/sandbox/") && filePath.includes("/outputs/"))) {
|
|
24000
|
+
return;
|
|
24001
|
+
}
|
|
24002
|
+
const outputsIndex = filePath.lastIndexOf("/outputs/");
|
|
24003
|
+
if (outputsIndex < 0) {
|
|
24004
|
+
return;
|
|
24005
|
+
}
|
|
24006
|
+
const sandboxDir = filePath.slice(0, outputsIndex);
|
|
24007
|
+
const filename = filePath.slice(outputsIndex + "/outputs/".length);
|
|
24008
|
+
const artifact = filename.replace(JSON_EXTENSION_REGEX, "");
|
|
24009
|
+
return { sandboxDir, artifact };
|
|
24010
|
+
}
|
|
24011
|
+
async function readArtifactData(filePath, artifact) {
|
|
24012
|
+
try {
|
|
24013
|
+
const content = await readFile4(filePath, "utf-8");
|
|
24014
|
+
return JSON.parse(content);
|
|
24015
|
+
} catch {
|
|
24016
|
+
console.error(`Validation failed for ${artifact}: Invalid JSON`);
|
|
24017
|
+
return;
|
|
24018
|
+
}
|
|
24019
|
+
}
|
|
24020
|
+
function validateWorkflowArtifact(data, criteria) {
|
|
24021
|
+
const checks = [];
|
|
24022
|
+
if (criteria.required_fields) {
|
|
24023
|
+
checks.push(...checkRequiredFields(data, criteria.required_fields));
|
|
24024
|
+
}
|
|
24025
|
+
if (criteria.min_quotes !== void 0) {
|
|
24026
|
+
checks.push(checkMinQuotes(data, criteria.min_quotes));
|
|
24027
|
+
}
|
|
24028
|
+
if (criteria.min_key_points !== void 0) {
|
|
24029
|
+
checks.push(checkMinKeyPoints(data, criteria.min_key_points));
|
|
24030
|
+
}
|
|
24031
|
+
if (criteria.min_outline_sections !== void 0) {
|
|
24032
|
+
checks.push(checkMinOutlineSections(data, criteria.min_outline_sections));
|
|
24033
|
+
}
|
|
24034
|
+
if (criteria.has_hooks === true) {
|
|
24035
|
+
checks.push(checkHasHooks(data));
|
|
24036
|
+
}
|
|
24037
|
+
const passed = checks.every((check2) => check2.passed);
|
|
24038
|
+
return { passed, checks };
|
|
24039
|
+
}
|
|
24040
|
+
function checkRequiredFields(data, fields) {
|
|
24041
|
+
return fields.map((field) => {
|
|
24042
|
+
const exists = hasField(data, field);
|
|
24043
|
+
return {
|
|
24044
|
+
name: `has_${field}`,
|
|
24045
|
+
passed: exists,
|
|
24046
|
+
message: exists ? "OK" : `Missing required field: ${field}`
|
|
24047
|
+
};
|
|
24048
|
+
});
|
|
24049
|
+
}
|
|
24050
|
+
function checkMinQuotes(data, minQuotes) {
|
|
24051
|
+
const quotes = getArrayLength(data, "importantQuotes");
|
|
24052
|
+
const passed = quotes >= minQuotes;
|
|
24053
|
+
return {
|
|
24054
|
+
name: "min_quotes",
|
|
24055
|
+
passed,
|
|
24056
|
+
message: passed ? `Found ${quotes} quotes (min: ${minQuotes})` : `Found ${quotes} quotes, need at least ${minQuotes}`
|
|
24057
|
+
};
|
|
24058
|
+
}
|
|
24059
|
+
function checkMinKeyPoints(data, minPoints) {
|
|
24060
|
+
const bullets = getArrayLength(data, "bullets");
|
|
24061
|
+
const keyPoints = getArrayLength(data, "keyPoints");
|
|
24062
|
+
const count = Math.max(bullets, keyPoints);
|
|
24063
|
+
const passed = count >= minPoints;
|
|
24064
|
+
return {
|
|
24065
|
+
name: "min_key_points",
|
|
24066
|
+
passed,
|
|
24067
|
+
message: passed ? `Found ${count} key points (min: ${minPoints})` : `Found ${count} key points, need at least ${minPoints}`
|
|
24068
|
+
};
|
|
24069
|
+
}
|
|
24070
|
+
function checkMinOutlineSections(data, minSections) {
|
|
24071
|
+
const sections = getArrayLength(data, "suggestedOutline");
|
|
24072
|
+
const passed = sections >= minSections;
|
|
24073
|
+
return {
|
|
24074
|
+
name: "min_outline_sections",
|
|
24075
|
+
passed,
|
|
24076
|
+
message: passed ? `Found ${sections} outline sections (min: ${minSections})` : `Found ${sections} sections, need at least ${minSections}`
|
|
24077
|
+
};
|
|
24078
|
+
}
|
|
24079
|
+
function checkHasHooks(data) {
|
|
24080
|
+
const hooksCount = getHooksCount(data);
|
|
24081
|
+
const passed = hooksCount > 0;
|
|
24082
|
+
return {
|
|
24083
|
+
name: "has_hooks",
|
|
24084
|
+
passed,
|
|
24085
|
+
message: passed ? `Found ${hooksCount} hooks` : "No hooks found in artifact"
|
|
24086
|
+
};
|
|
24087
|
+
}
|
|
24088
|
+
function getHooksCount(data) {
|
|
24089
|
+
const ideas = data.ideas;
|
|
24090
|
+
if (ideas && Array.isArray(ideas.hooks)) {
|
|
24091
|
+
return ideas.hooks.length;
|
|
24092
|
+
}
|
|
24093
|
+
if (Array.isArray(data.hooks)) {
|
|
24094
|
+
return data.hooks.length;
|
|
24095
|
+
}
|
|
24096
|
+
return 0;
|
|
24097
|
+
}
|
|
24098
|
+
function hasField(data, field) {
|
|
24099
|
+
const parts = field.split(".");
|
|
24100
|
+
let current = data;
|
|
24101
|
+
for (const part of parts) {
|
|
24102
|
+
if (current === null || typeof current !== "object") {
|
|
24103
|
+
return false;
|
|
24104
|
+
}
|
|
24105
|
+
current = current[part];
|
|
24106
|
+
}
|
|
24107
|
+
return current !== void 0 && current !== null;
|
|
24108
|
+
}
|
|
24109
|
+
function getArrayLength(data, field) {
|
|
24110
|
+
const value = data[field];
|
|
24111
|
+
return Array.isArray(value) ? value.length : 0;
|
|
24112
|
+
}
|
|
24113
|
+
function validateArtifactData(artifact, artifactData, criteria) {
|
|
24114
|
+
if (criteria === void 0 || criteria === null) {
|
|
24115
|
+
return true;
|
|
24116
|
+
}
|
|
24117
|
+
if (!isRecord(criteria)) {
|
|
24118
|
+
console.error(`Validation failed for ${artifact}: Invalid criteria`);
|
|
24119
|
+
return false;
|
|
24120
|
+
}
|
|
24121
|
+
const result = validateWorkflowArtifact(
|
|
24122
|
+
artifactData,
|
|
24123
|
+
criteria
|
|
24124
|
+
);
|
|
24125
|
+
if (!result.passed) {
|
|
24126
|
+
console.error(`Semantic validation failed for ${artifact}:`);
|
|
24127
|
+
console.error(JSON.stringify(result, null, 2));
|
|
24128
|
+
}
|
|
24129
|
+
return result.passed;
|
|
24130
|
+
}
|
|
24131
|
+
async function sleep2(ms) {
|
|
24132
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
24133
|
+
}
|
|
24134
|
+
async function acquireLock(lockPath, timeoutMs) {
|
|
24135
|
+
const start = Date.now();
|
|
24136
|
+
while (Date.now() - start < timeoutMs) {
|
|
24137
|
+
try {
|
|
24138
|
+
const handle = await open2(lockPath, "wx");
|
|
24139
|
+
return {
|
|
24140
|
+
release: async () => {
|
|
24141
|
+
await handle.close();
|
|
24142
|
+
try {
|
|
24143
|
+
await unlink(lockPath);
|
|
24144
|
+
} catch {
|
|
24145
|
+
}
|
|
24146
|
+
}
|
|
24147
|
+
};
|
|
24148
|
+
} catch (error2) {
|
|
24149
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "EEXIST") {
|
|
24150
|
+
await sleep2(LOCK_RETRY_DELAY_MS2);
|
|
24151
|
+
continue;
|
|
24152
|
+
}
|
|
24153
|
+
throw error2;
|
|
24154
|
+
}
|
|
24155
|
+
}
|
|
24156
|
+
return;
|
|
24157
|
+
}
|
|
24158
|
+
async function writeValidationManifest(validationPath, manifest) {
|
|
24159
|
+
const tempPath = `${validationPath}.tmp`;
|
|
24160
|
+
await writeFile3(tempPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
24161
|
+
await rename2(tempPath, validationPath);
|
|
24162
|
+
}
|
|
24163
|
+
async function findMissingOutputs(sandboxDir, steps) {
|
|
24164
|
+
const missing = [];
|
|
24165
|
+
for (const [stepId, stepState] of Object.entries(steps)) {
|
|
24166
|
+
const outputPath = stepState.output;
|
|
24167
|
+
if (!outputPath) {
|
|
24168
|
+
continue;
|
|
24169
|
+
}
|
|
24170
|
+
const fullPath = join7(sandboxDir, outputPath);
|
|
24171
|
+
try {
|
|
24172
|
+
await stat2(fullPath);
|
|
24173
|
+
} catch {
|
|
24174
|
+
missing.push(stepId);
|
|
24175
|
+
}
|
|
24176
|
+
}
|
|
24177
|
+
return missing;
|
|
24178
|
+
}
|
|
24179
|
+
function findPendingSteps(steps) {
|
|
24180
|
+
return Object.entries(steps).filter(([, stepState]) => !stepState.validated).map(([stepId]) => stepId);
|
|
24181
|
+
}
|
|
24182
|
+
function createStopGuardHook(context) {
|
|
24183
|
+
return async (input) => {
|
|
24184
|
+
const hookInput = input;
|
|
24185
|
+
if (hookInput.stop_hook_active === true) {
|
|
24186
|
+
return {};
|
|
24187
|
+
}
|
|
24188
|
+
const sandboxDir = await resolveSandboxDir2(context);
|
|
24189
|
+
if (!sandboxDir) {
|
|
24190
|
+
return {};
|
|
24191
|
+
}
|
|
24192
|
+
const manifest = await readValidationManifest2(sandboxDir);
|
|
24193
|
+
if (!manifest) {
|
|
24194
|
+
return {};
|
|
24195
|
+
}
|
|
24196
|
+
const missingOutputs = await findMissingOutputs(sandboxDir, manifest.steps);
|
|
24197
|
+
if (missingOutputs.length > 0) {
|
|
24198
|
+
const stepsList = missingOutputs.join(", ");
|
|
24199
|
+
return {
|
|
24200
|
+
decision: "block",
|
|
24201
|
+
reason: `Your workflow is not complete. You still need to create output files for these steps: ${stepsList}. Please continue working on the workflow by using the Write tool to create the required JSON files at the paths specified in validation.json for each incomplete step. Do not stop until all workflow steps have their output files created.`
|
|
24202
|
+
};
|
|
24203
|
+
}
|
|
24204
|
+
const pendingSteps = findPendingSteps(manifest.steps);
|
|
24205
|
+
if (pendingSteps.length > 0) {
|
|
24206
|
+
const stepsList = pendingSteps.join(", ");
|
|
24207
|
+
return {
|
|
24208
|
+
decision: "block",
|
|
24209
|
+
reason: `Your workflow is not complete. The following steps need validation: ${stepsList}. The output files exist but have not been validated yet. Please re-write these output files using the Write tool to trigger validation. Do not stop until all workflow steps are validated.`
|
|
24210
|
+
};
|
|
24211
|
+
}
|
|
24212
|
+
return {};
|
|
24213
|
+
};
|
|
24214
|
+
}
|
|
24215
|
+
var stopGuardHook = createStopGuardHook();
|
|
24216
|
+
function createPostWriteValidateHook() {
|
|
24217
|
+
return async (input) => {
|
|
24218
|
+
const hookInput = input;
|
|
24219
|
+
const filePath = hookInput.tool_input?.file_path;
|
|
24220
|
+
const artifactInfo = getSandboxArtifactInfo(filePath);
|
|
24221
|
+
if (!(artifactInfo && filePath)) {
|
|
24222
|
+
return {};
|
|
24223
|
+
}
|
|
24224
|
+
const artifactData = await readArtifactData(
|
|
24225
|
+
filePath,
|
|
24226
|
+
artifactInfo.artifact
|
|
24227
|
+
);
|
|
24228
|
+
if (!artifactData) {
|
|
24229
|
+
return {};
|
|
24230
|
+
}
|
|
24231
|
+
const validationPath = join7(artifactInfo.sandboxDir, "validation.json");
|
|
24232
|
+
const lock = await acquireLock(`${validationPath}.lock`, LOCK_TIMEOUT_MS);
|
|
24233
|
+
if (!lock) {
|
|
24234
|
+
console.error("Failed to acquire lock on validation.json");
|
|
24235
|
+
return {};
|
|
24236
|
+
}
|
|
24237
|
+
try {
|
|
24238
|
+
const manifest = await readValidationManifest2(artifactInfo.sandboxDir);
|
|
24239
|
+
if (!manifest) {
|
|
24240
|
+
return {};
|
|
24241
|
+
}
|
|
24242
|
+
const stepState = manifest.steps[artifactInfo.artifact];
|
|
24243
|
+
if (!stepState) {
|
|
24244
|
+
return {};
|
|
24245
|
+
}
|
|
24246
|
+
const isValid3 = validateArtifactData(
|
|
24247
|
+
artifactInfo.artifact,
|
|
24248
|
+
artifactData,
|
|
24249
|
+
stepState.validate
|
|
24250
|
+
);
|
|
24251
|
+
if (!isValid3) {
|
|
24252
|
+
return {};
|
|
24253
|
+
}
|
|
24254
|
+
manifest.steps[artifactInfo.artifact] = {
|
|
24255
|
+
...stepState,
|
|
24256
|
+
validated: true,
|
|
24257
|
+
validatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24258
|
+
};
|
|
24259
|
+
await writeValidationManifest(validationPath, manifest);
|
|
24260
|
+
console.error(`\u2713 Validated: ${artifactInfo.artifact}.json`);
|
|
24261
|
+
} finally {
|
|
24262
|
+
await lock.release();
|
|
24263
|
+
}
|
|
24264
|
+
return {};
|
|
24265
|
+
};
|
|
24266
|
+
}
|
|
24267
|
+
var postWriteValidateHook = createPostWriteValidateHook();
|
|
24268
|
+
function createWorkflowHooks() {
|
|
24269
|
+
const context = {
|
|
24270
|
+
sandboxId: process.env.LOOPLIA_SANDBOX_ID,
|
|
24271
|
+
sandboxRoot: process.env.LOOPLIA_SANDBOX_ROOT
|
|
24272
|
+
};
|
|
24273
|
+
return {
|
|
24274
|
+
Stop: [{ hooks: [createStopGuardHook(context)] }],
|
|
24275
|
+
SubagentStop: [{ hooks: [createStopGuardHook(context)] }],
|
|
24276
|
+
PostToolUse: [{ matcher: "Write", hooks: [createPostWriteValidateHook()] }]
|
|
24277
|
+
};
|
|
24278
|
+
}
|
|
23636
24279
|
var SUMMARIZE_SYSTEM_PROMPT = `You are an expert content analyst specializing in summarization and content intelligence.
|
|
23637
24280
|
|
|
23638
24281
|
Your expertise includes:
|
|
@@ -23663,7 +24306,7 @@ The content-analyzer agent will:
|
|
|
23663
24306
|
3. Use content-documenter skill for structured documentation
|
|
23664
24307
|
4. Write results to sandbox/${content.id}/outputs/summary.json`;
|
|
23665
24308
|
}
|
|
23666
|
-
async function* executeInteractiveQueryStreaming(prompt,
|
|
24309
|
+
async function* executeInteractiveQueryStreaming(prompt, _jsonSchema, config2, questionCallback) {
|
|
23667
24310
|
const resolvedConfig = resolveConfig(config2);
|
|
23668
24311
|
await initializeAndValidateApiKey(config2);
|
|
23669
24312
|
const mainModel = getMainModel();
|
|
@@ -23761,8 +24404,9 @@ When processing --file arguments or user file paths, resolve them against the Us
|
|
|
23761
24404
|
"WebFetch",
|
|
23762
24405
|
"AskUserQuestion"
|
|
23763
24406
|
// Only in interactive mode
|
|
23764
|
-
]
|
|
23765
|
-
|
|
24407
|
+
]
|
|
24408
|
+
// v0.7.2: Removed outputFormat to support non-Anthropic models
|
|
24409
|
+
// Final results are now extracted from sandbox output files via extractSandboxResult()
|
|
23766
24410
|
}
|
|
23767
24411
|
});
|
|
23768
24412
|
let finalResult;
|
|
@@ -23786,6 +24430,12 @@ When processing --file arguments or user file paths, resolve them against the Us
|
|
|
23786
24430
|
}
|
|
23787
24431
|
logger.close();
|
|
23788
24432
|
yield progressTracker.onComplete();
|
|
24433
|
+
if (!finalResult) {
|
|
24434
|
+
finalResult = await extractSandboxResult({
|
|
24435
|
+
sandboxId: process.env.LOOPLIA_SANDBOX_ID,
|
|
24436
|
+
sandboxRoot: process.env.LOOPLIA_SANDBOX_ROOT ?? join8(workspace, "sandbox")
|
|
24437
|
+
});
|
|
24438
|
+
}
|
|
23789
24439
|
return finalResult ?? {
|
|
23790
24440
|
success: false,
|
|
23791
24441
|
error: { type: "unknown", message: "No result received" }
|
|
@@ -24147,6 +24797,7 @@ export {
|
|
|
24147
24797
|
writeUserProfile,
|
|
24148
24798
|
executeAgenticQueryStreaming,
|
|
24149
24799
|
createClaudeAgentExecutor,
|
|
24800
|
+
createWorkflowHooks,
|
|
24150
24801
|
SUMMARIZE_SYSTEM_PROMPT,
|
|
24151
24802
|
buildSummarizePrompt,
|
|
24152
24803
|
executeInteractiveQueryStreaming,
|