agent-relay 3.2.18 → 3.2.22
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/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +233 -55
- package/dist/src/cli/commands/cloud.d.ts +1 -9
- package/dist/src/cli/commands/cloud.d.ts.map +1 -1
- package/dist/src/cli/commands/cloud.js +326 -323
- package/dist/src/cli/commands/cloud.js.map +1 -1
- package/dist/src/cli/commands/connect.d.ts.map +1 -1
- package/dist/src/cli/commands/connect.js +6 -10
- package/dist/src/cli/commands/connect.js.map +1 -1
- package/package.json +16 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/brand/README.md +36 -0
- package/packages/brand/brand.css +226 -0
- package/packages/brand/package.json +20 -0
- package/packages/cloud/dist/api-client.d.ts +33 -0
- package/packages/cloud/dist/api-client.d.ts.map +1 -0
- package/packages/cloud/dist/api-client.js +123 -0
- package/packages/cloud/dist/api-client.js.map +1 -0
- package/packages/cloud/dist/auth.d.ts +13 -0
- package/packages/cloud/dist/auth.d.ts.map +1 -0
- package/packages/cloud/dist/auth.js +248 -0
- package/packages/cloud/dist/auth.js.map +1 -0
- package/packages/cloud/dist/index.d.ts +5 -0
- package/packages/cloud/dist/index.d.ts.map +1 -0
- package/packages/cloud/dist/index.js +5 -0
- package/packages/cloud/dist/index.js.map +1 -0
- package/packages/cloud/dist/types.d.ts +73 -0
- package/packages/cloud/dist/types.d.ts.map +1 -0
- package/packages/cloud/dist/types.js +19 -0
- package/packages/cloud/dist/types.js.map +1 -0
- package/packages/cloud/dist/workflows.d.ts +34 -0
- package/packages/cloud/dist/workflows.d.ts.map +1 -0
- package/packages/cloud/dist/workflows.js +389 -0
- package/packages/cloud/dist/workflows.js.map +1 -0
- package/packages/cloud/package.json +44 -0
- package/packages/cloud/src/api-client.ts +169 -0
- package/packages/cloud/src/auth.ts +314 -0
- package/packages/cloud/src/index.ts +41 -0
- package/packages/cloud/src/types.ts +97 -0
- package/packages/cloud/src/workflows.ts +539 -0
- package/packages/cloud/tsconfig.json +21 -0
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.js +62 -0
- package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.js.map +1 -0
- package/packages/sdk/dist/workflows/cli.js +46 -2
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/file-db.d.ts +2 -0
- package/packages/sdk/dist/workflows/file-db.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/file-db.js +20 -3
- package/packages/sdk/dist/workflows/file-db.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -1
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +233 -50
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/__tests__/resume-fallback.test.ts +415 -0
- package/packages/sdk/src/__tests__/workflow-runner.test.ts +73 -2
- package/packages/sdk/src/workflows/__tests__/e2big-and-verify.test.ts +117 -0
- package/packages/sdk/src/workflows/cli.ts +53 -2
- package/packages/sdk/src/workflows/file-db.ts +22 -3
- package/packages/sdk/src/workflows/runner.ts +283 -49
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayObserver.swift +2 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/index.cjs
CHANGED
|
@@ -9318,11 +9318,11 @@ __export(base_exports, {
|
|
|
9318
9318
|
setCwd: () => setCwd,
|
|
9319
9319
|
synchronizedOutput: () => synchronizedOutput
|
|
9320
9320
|
});
|
|
9321
|
-
var import_node_process,
|
|
9321
|
+
var import_node_process, import_node_os9, ESC, OSC, BEL, SEP, isTerminalApp, isWindows2, isTmux, cwdFunction, wrapOsc, cursorTo, cursorMove, cursorUp, cursorDown, cursorForward, cursorBackward, cursorLeft, cursorSavePosition, cursorRestorePosition, cursorGetPosition, cursorNextLine, cursorPrevLine, cursorHide, cursorShow, eraseLines, eraseEndLine, eraseStartLine, eraseLine, eraseDown, eraseUp, eraseScreen, scrollUp, scrollDown, clearScreen, clearViewport, isOldWindows, clearTerminal, enterAlternativeScreen, exitAlternativeScreen, beginSynchronizedOutput, endSynchronizedOutput, synchronizedOutput, beep, link, image, iTerm, ConEmu, setCwd;
|
|
9322
9322
|
var init_base = __esm({
|
|
9323
9323
|
"node_modules/ansi-escapes/base.js"() {
|
|
9324
9324
|
import_node_process = __toESM(require("node:process"), 1);
|
|
9325
|
-
|
|
9325
|
+
import_node_os9 = __toESM(require("node:os"), 1);
|
|
9326
9326
|
init_environment();
|
|
9327
9327
|
ESC = "\x1B[";
|
|
9328
9328
|
OSC = "\x1B]";
|
|
@@ -9402,7 +9402,7 @@ var init_base = __esm({
|
|
|
9402
9402
|
if (isBrowser || !isWindows2) {
|
|
9403
9403
|
return false;
|
|
9404
9404
|
}
|
|
9405
|
-
const parts =
|
|
9405
|
+
const parts = import_node_os9.default.release().split(".");
|
|
9406
9406
|
const major = Number(parts[0]);
|
|
9407
9407
|
const build = Number(parts[2] ?? 0);
|
|
9408
9408
|
if (major < 10) {
|
|
@@ -41321,6 +41321,7 @@ var import_node_child_process4 = require("node:child_process");
|
|
|
41321
41321
|
var import_node_crypto5 = require("node:crypto");
|
|
41322
41322
|
var import_node_fs9 = require("node:fs");
|
|
41323
41323
|
var import_promises5 = require("node:fs/promises");
|
|
41324
|
+
var import_node_os8 = require("node:os");
|
|
41324
41325
|
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
41325
41326
|
var import_chalk = __toESM(require_source(), 1);
|
|
41326
41327
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
@@ -43465,6 +43466,8 @@ var WorkflowRunner = class _WorkflowRunner {
|
|
|
43465
43466
|
activeReviewers = /* @__PURE__ */ new Map();
|
|
43466
43467
|
/** Structured CLI session reports captured during the current run, keyed by step name. */
|
|
43467
43468
|
agentReports = /* @__PURE__ */ new Map();
|
|
43469
|
+
static PTY_TASK_ARG_SIZE_LIMIT = 2 * 1024 * 1024;
|
|
43470
|
+
// 2 MB
|
|
43468
43471
|
constructor(options = {}) {
|
|
43469
43472
|
this.db = options.db ?? new InMemoryWorkflowDb();
|
|
43470
43473
|
this.workspaceId = options.workspaceId ?? "local";
|
|
@@ -44708,33 +44711,45 @@ ${err.suggestion}`);
|
|
|
44708
44711
|
});
|
|
44709
44712
|
}
|
|
44710
44713
|
/** Resume a previously paused or partially completed run. */
|
|
44711
|
-
async resume(runId, vars) {
|
|
44714
|
+
async resume(runId, vars, config2) {
|
|
44712
44715
|
this.abortController = new AbortController();
|
|
44713
44716
|
this.paused = false;
|
|
44714
|
-
|
|
44717
|
+
let run = await this.db.getRun(runId);
|
|
44718
|
+
let stepStates = /* @__PURE__ */ new Map();
|
|
44715
44719
|
if (!run) {
|
|
44716
|
-
|
|
44720
|
+
const reconstructed = this.reconstructRunFromCache(runId, config2);
|
|
44721
|
+
if (!reconstructed) {
|
|
44722
|
+
throw new Error(`Run "${runId}" not found (no database entry or cached step outputs)`);
|
|
44723
|
+
}
|
|
44724
|
+
this.log("[resume] Reconstructing run from cached step outputs (workflow-runs.jsonl missing)");
|
|
44725
|
+
run = reconstructed.run;
|
|
44726
|
+
stepStates = reconstructed.stepStates;
|
|
44727
|
+
await this.db.insertRun(run);
|
|
44728
|
+
for (const [, state] of stepStates) {
|
|
44729
|
+
await this.db.insertStep(state.row);
|
|
44730
|
+
}
|
|
44717
44731
|
}
|
|
44718
44732
|
this.persistRunIdHint(runId);
|
|
44719
44733
|
if (run.status !== "running" && run.status !== "failed") {
|
|
44720
44734
|
throw new Error(`Run "${runId}" is in status "${run.status}" and cannot be resumed`);
|
|
44721
44735
|
}
|
|
44722
|
-
const
|
|
44723
|
-
const pathResult = this.resolvePathDefinitions(
|
|
44736
|
+
const resolvedConfig = vars ? this.resolveVariables(run.config, vars) : run.config;
|
|
44737
|
+
const pathResult = this.resolvePathDefinitions(resolvedConfig.paths, this.cwd);
|
|
44724
44738
|
if (pathResult.errors.length > 0) {
|
|
44725
44739
|
throw new Error(`Path validation failed:
|
|
44726
44740
|
${pathResult.errors.join("\n ")}`);
|
|
44727
44741
|
}
|
|
44728
44742
|
this.resolvedPaths = pathResult.resolved;
|
|
44729
|
-
const workflows =
|
|
44743
|
+
const workflows = resolvedConfig.workflows ?? [];
|
|
44730
44744
|
const workflow2 = workflows.find((w) => w.name === run.workflowName);
|
|
44731
44745
|
if (!workflow2) {
|
|
44732
44746
|
throw new Error(`Workflow "${run.workflowName}" not found in stored config`);
|
|
44733
44747
|
}
|
|
44734
|
-
|
|
44735
|
-
|
|
44736
|
-
|
|
44737
|
-
|
|
44748
|
+
if (stepStates.size === 0) {
|
|
44749
|
+
const existingSteps = await this.db.getStepsByRunId(runId);
|
|
44750
|
+
for (const stepRow of existingSteps) {
|
|
44751
|
+
stepStates.set(stepRow.stepName, { row: stepRow });
|
|
44752
|
+
}
|
|
44738
44753
|
}
|
|
44739
44754
|
for (const [, state] of stepStates) {
|
|
44740
44755
|
if (state.row.status === "failed") {
|
|
@@ -44752,7 +44767,7 @@ ${err.suggestion}`);
|
|
|
44752
44767
|
return this.runWorkflowCore({
|
|
44753
44768
|
run,
|
|
44754
44769
|
workflow: workflow2,
|
|
44755
|
-
config:
|
|
44770
|
+
config: resolvedConfig,
|
|
44756
44771
|
stepStates,
|
|
44757
44772
|
isResume: true
|
|
44758
44773
|
});
|
|
@@ -45887,6 +45902,7 @@ ${resolvedTask}`;
|
|
|
45887
45902
|
let ownerOutput;
|
|
45888
45903
|
let ownerElapsed;
|
|
45889
45904
|
let completionReason;
|
|
45905
|
+
let promptTaskText;
|
|
45890
45906
|
if (usesDedicatedOwner) {
|
|
45891
45907
|
const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs);
|
|
45892
45908
|
specialistOutput = result.specialistOutput;
|
|
@@ -45919,13 +45935,14 @@ ${resolvedTask}`;
|
|
|
45919
45935
|
} : void 0
|
|
45920
45936
|
});
|
|
45921
45937
|
const output = typeof spawnResult === "string" ? spawnResult : spawnResult.output;
|
|
45938
|
+
promptTaskText = typeof spawnResult === "string" ? effectiveOwner.interactive === false ? void 0 : ownerTask : spawnResult.promptTaskText ?? ownerTask;
|
|
45922
45939
|
lastExitCode = typeof spawnResult === "string" ? void 0 : spawnResult.exitCode;
|
|
45923
45940
|
lastExitSignal = typeof spawnResult === "string" ? void 0 : spawnResult.exitSignal;
|
|
45924
45941
|
ownerElapsed = Date.now() - ownerStartTime;
|
|
45925
45942
|
this.log(`[${step.name}] Owner "${effectiveOwner.name}" exited`);
|
|
45926
45943
|
if (usesOwnerFlow) {
|
|
45927
45944
|
try {
|
|
45928
|
-
const completionDecision = this.resolveOwnerCompletionDecision(step, output, output, ownerTask,
|
|
45945
|
+
const completionDecision = this.resolveOwnerCompletionDecision(step, output, output, promptTaskText ?? ownerTask, promptTaskText ?? ownerTask);
|
|
45929
45946
|
completionReason = completionDecision.completionReason;
|
|
45930
45947
|
} catch (error48) {
|
|
45931
45948
|
const canUseVerificationFallback = !usesDedicatedOwner && step.verification && error48 instanceof WorkflowCompletionError && error48.completionReason === "failed_no_evidence";
|
|
@@ -45950,7 +45967,7 @@ ${resolvedTask}`;
|
|
|
45950
45967
|
}
|
|
45951
45968
|
}
|
|
45952
45969
|
if (step.verification && (!usesOwnerFlow || !usesDedicatedOwner) && !completionReason) {
|
|
45953
|
-
const verificationResult = this.runVerification(step.verification, specialistOutput, step.name,
|
|
45970
|
+
const verificationResult = this.runVerification(step.verification, specialistOutput, step.name, promptTaskText);
|
|
45954
45971
|
completionReason = verificationResult.completionReason;
|
|
45955
45972
|
}
|
|
45956
45973
|
if (completionReason === "retry_requested_by_owner") {
|
|
@@ -46186,7 +46203,7 @@ WORKER COMPLETION CONTRACT:
|
|
|
46186
46203
|
detail: `Worker ${workerRuntimeName} exited`,
|
|
46187
46204
|
raw: { worker: workerRuntimeName, exitCode: result.exitCode, exitSignal: result.exitSignal }
|
|
46188
46205
|
});
|
|
46189
|
-
if (step.verification?.type === "output_contains" && result.output
|
|
46206
|
+
if (step.verification?.type === "output_contains" && this.outputContainsVerificationToken(result.output, step.verification.value, result.promptTaskText)) {
|
|
46190
46207
|
this.log(`[${step.name}] Verification gate observed: output contains ${JSON.stringify(step.verification.value)}`);
|
|
46191
46208
|
}
|
|
46192
46209
|
}).catch((error48) => {
|
|
@@ -46228,8 +46245,9 @@ WORKER COMPLETION CONTRACT:
|
|
|
46228
46245
|
const ownerElapsed = Date.now() - ownerStartTime;
|
|
46229
46246
|
const ownerOutput = ownerResultObj.output;
|
|
46230
46247
|
this.log(`[${step.name}] Owner "${supervised.owner.name}" exited`);
|
|
46231
|
-
const
|
|
46232
|
-
const
|
|
46248
|
+
const workerResultObj = await workerPromise;
|
|
46249
|
+
const specialistOutput = workerResultObj.output;
|
|
46250
|
+
const completionDecision = this.resolveOwnerCompletionDecision(step, ownerOutput, specialistOutput, ownerResultObj.promptTaskText ?? supervisorTask, workerResultObj.promptTaskText ?? specialistTask);
|
|
46233
46251
|
return {
|
|
46234
46252
|
specialistOutput,
|
|
46235
46253
|
ownerOutput,
|
|
@@ -46417,6 +46435,10 @@ WORKER COMPLETION CONTRACT:
|
|
|
46417
46435
|
}
|
|
46418
46436
|
hasOwnerCompletionMarker(step, output, injectedTaskText) {
|
|
46419
46437
|
const marker = `STEP_COMPLETE:${step.name}`;
|
|
46438
|
+
const strippedOutput = this.stripInjectedTaskEcho(output, injectedTaskText);
|
|
46439
|
+
if (strippedOutput.includes(marker)) {
|
|
46440
|
+
return true;
|
|
46441
|
+
}
|
|
46420
46442
|
const taskHasMarker = injectedTaskText.includes(marker);
|
|
46421
46443
|
const first = output.indexOf(marker);
|
|
46422
46444
|
if (first === -1) {
|
|
@@ -46463,6 +46485,49 @@ WORKER COMPLETION CONTRACT:
|
|
|
46463
46485
|
stripEchoedPromptLines(output, patterns) {
|
|
46464
46486
|
return output.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => patterns.every((pattern) => !pattern.test(line))).join("\n");
|
|
46465
46487
|
}
|
|
46488
|
+
stripInjectedTaskEcho(output, injectedTaskText) {
|
|
46489
|
+
if (!injectedTaskText) {
|
|
46490
|
+
return output;
|
|
46491
|
+
}
|
|
46492
|
+
const candidates = [
|
|
46493
|
+
injectedTaskText,
|
|
46494
|
+
injectedTaskText.replace(/\r\n/g, "\n"),
|
|
46495
|
+
injectedTaskText.replace(/\n/g, "\r\n")
|
|
46496
|
+
].filter((candidate, index, all) => candidate.length > 0 && all.indexOf(candidate) === index);
|
|
46497
|
+
for (const candidate of candidates) {
|
|
46498
|
+
const start = output.indexOf(candidate);
|
|
46499
|
+
if (start !== -1) {
|
|
46500
|
+
return output.slice(0, start) + output.slice(start + candidate.length);
|
|
46501
|
+
}
|
|
46502
|
+
}
|
|
46503
|
+
return output;
|
|
46504
|
+
}
|
|
46505
|
+
outputContainsVerificationToken(output, token, injectedTaskText) {
|
|
46506
|
+
if (!token) {
|
|
46507
|
+
return false;
|
|
46508
|
+
}
|
|
46509
|
+
return this.stripInjectedTaskEcho(output, injectedTaskText).includes(token);
|
|
46510
|
+
}
|
|
46511
|
+
prepareInteractiveSpawnTask(agentName, taskText) {
|
|
46512
|
+
if (Buffer.byteLength(taskText, "utf8") <= _WorkflowRunner.PTY_TASK_ARG_SIZE_LIMIT) {
|
|
46513
|
+
return {
|
|
46514
|
+
spawnTaskText: taskText,
|
|
46515
|
+
promptTaskText: taskText
|
|
46516
|
+
};
|
|
46517
|
+
}
|
|
46518
|
+
const taskTmpDir = (0, import_node_fs9.mkdtempSync)(import_node_path12.default.join((0, import_node_os8.tmpdir)(), "relay-pty-task-"));
|
|
46519
|
+
const taskTmpFile = import_node_path12.default.join(taskTmpDir, `${agentName}-${Date.now()}.txt`);
|
|
46520
|
+
(0, import_node_fs9.writeFileSync)(taskTmpFile, taskText, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
46521
|
+
const promptTaskText = `TASK_FILE:${taskTmpFile}
|
|
46522
|
+
Read that file completely before taking any action.
|
|
46523
|
+
Treat the file contents as the full workflow task and follow them exactly.
|
|
46524
|
+
Do not ask for the task again.`;
|
|
46525
|
+
return {
|
|
46526
|
+
spawnTaskText: promptTaskText,
|
|
46527
|
+
promptTaskText,
|
|
46528
|
+
taskTmpFile
|
|
46529
|
+
};
|
|
46530
|
+
}
|
|
46466
46531
|
firstMeaningfulLine(output) {
|
|
46467
46532
|
return output.split("\n").map((line) => line.trim()).find(Boolean);
|
|
46468
46533
|
}
|
|
@@ -47000,6 +47065,7 @@ DO NOT:
|
|
|
47000
47065
|
const delegationGuidance = isHub || !isHubPattern ? this.buildDelegationGuidance(agentDef.cli, timeoutMs) : "";
|
|
47001
47066
|
const relayRegistrationNote = this.buildRelayRegistrationNote(agentDef.cli, agentName);
|
|
47002
47067
|
const taskWithExit = step.task + (relayRegistrationNote ? "\n\n" + relayRegistrationNote : "") + (delegationGuidance ? "\n\n" + delegationGuidance + "\n" : "") + '\n\n---\nIMPORTANT: When you have fully completed this task, you MUST self-terminate by either: (a) calling remove_agent(name: "<your-agent-name>", reason: "task completed") \u2014 preferred, or (b) outputting the exact text "/exit" on its own line as a fallback. Do not wait for further input \u2014 terminate immediately after finishing. Do NOT spawn sub-agents unless the task explicitly requires it.';
|
|
47068
|
+
const preparedTask = this.prepareInteractiveSpawnTask(agentName, taskWithExit);
|
|
47003
47069
|
this.ptyOutputBuffers.set(agentName, []);
|
|
47004
47070
|
const logsDir = this.getWorkerLogsDir();
|
|
47005
47071
|
const logStream = (0, import_node_fs9.createWriteStream)(import_node_path12.default.join(logsDir, `${agentName}.log`), { flags: "a" });
|
|
@@ -47029,7 +47095,7 @@ DO NOT:
|
|
|
47029
47095
|
model: agentDef.constraints?.model,
|
|
47030
47096
|
args: interactiveSpawnPolicy.args,
|
|
47031
47097
|
channels: agentChannels,
|
|
47032
|
-
task:
|
|
47098
|
+
task: preparedTask.spawnTaskText,
|
|
47033
47099
|
idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
|
|
47034
47100
|
cwd: agentCwd
|
|
47035
47101
|
});
|
|
@@ -47097,13 +47163,13 @@ DO NOT:
|
|
|
47097
47163
|
}
|
|
47098
47164
|
this.log(`[${step.name}] Assigned to ${agent.name}`);
|
|
47099
47165
|
this.activeAgentHandles.set(agentName, agent);
|
|
47100
|
-
exitResult = await this.waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, options.preserveOnIdle ?? this.shouldPreserveIdleSupervisor(agentDef, step, options.evidenceRole));
|
|
47166
|
+
exitResult = await this.waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, preparedTask.promptTaskText, options.preserveOnIdle ?? this.shouldPreserveIdleSupervisor(agentDef, step, options.evidenceRole));
|
|
47101
47167
|
stopHeartbeat?.();
|
|
47102
47168
|
if (exitResult === "timeout") {
|
|
47103
47169
|
let timeoutRecovered = false;
|
|
47104
47170
|
if (step.verification) {
|
|
47105
47171
|
const ptyOutput = (this.ptyOutputBuffers.get(agentName) ?? []).join("");
|
|
47106
|
-
const verificationResult = this.runVerification(step.verification, ptyOutput, step.name,
|
|
47172
|
+
const verificationResult = this.runVerification(step.verification, ptyOutput, step.name, preparedTask.promptTaskText, { allowFailure: true });
|
|
47107
47173
|
if (verificationResult.passed) {
|
|
47108
47174
|
this.log(`[${step.name}] Agent timed out but verification passed \u2014 treating as complete`);
|
|
47109
47175
|
this.postToChannel(`**[${step.name}]** Agent idle after completing work \u2014 verification passed, releasing`);
|
|
@@ -47147,6 +47213,9 @@ DO NOT:
|
|
|
47147
47213
|
this.unregisterWorker(agentName);
|
|
47148
47214
|
this.supervisedRuntimeAgents.delete(agentName);
|
|
47149
47215
|
this.runtimeStepAgents.delete(agentName);
|
|
47216
|
+
if (preparedTask.taskTmpFile) {
|
|
47217
|
+
await (0, import_promises5.unlink)(preparedTask.taskTmpFile).catch(() => void 0);
|
|
47218
|
+
}
|
|
47150
47219
|
}
|
|
47151
47220
|
let output;
|
|
47152
47221
|
if (ptyChunks.length > 0) {
|
|
@@ -47165,7 +47234,8 @@ DO NOT:
|
|
|
47165
47234
|
return {
|
|
47166
47235
|
output,
|
|
47167
47236
|
exitCode: agent?.exitCode,
|
|
47168
|
-
exitSignal: agent?.exitSignal
|
|
47237
|
+
exitSignal: agent?.exitSignal,
|
|
47238
|
+
promptTaskText: preparedTask.promptTaskText
|
|
47169
47239
|
};
|
|
47170
47240
|
}
|
|
47171
47241
|
// ── Idle nudging ────────────────────────────────────────────────────────
|
|
@@ -47210,7 +47280,7 @@ DO NOT:
|
|
|
47210
47280
|
* Wait for agent exit with idle detection and nudging.
|
|
47211
47281
|
* If no idle nudge config is set, falls through to simple waitForExit.
|
|
47212
47282
|
*/
|
|
47213
|
-
async waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, preserveIdleSupervisor = false) {
|
|
47283
|
+
async waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, promptTaskText, preserveIdleSupervisor = false) {
|
|
47214
47284
|
const nudgeConfig = this.currentConfig?.swarm.idleNudge;
|
|
47215
47285
|
if (!nudgeConfig) {
|
|
47216
47286
|
if (preserveIdleSupervisor) {
|
|
@@ -47232,15 +47302,7 @@ DO NOT:
|
|
|
47232
47302
|
if (step.verification && step.verification.type === "output_contains") {
|
|
47233
47303
|
const token = step.verification.value;
|
|
47234
47304
|
const ptyOutput = (this.ptyOutputBuffers.get(agent.name) ?? []).join("");
|
|
47235
|
-
const
|
|
47236
|
-
const taskHasToken = taskText.includes(token);
|
|
47237
|
-
let verificationPassed = true;
|
|
47238
|
-
if (taskHasToken) {
|
|
47239
|
-
const first = ptyOutput.indexOf(token);
|
|
47240
|
-
verificationPassed = first !== -1 && ptyOutput.includes(token, first + token.length);
|
|
47241
|
-
} else {
|
|
47242
|
-
verificationPassed = ptyOutput.includes(token);
|
|
47243
|
-
}
|
|
47305
|
+
const verificationPassed = this.outputContainsVerificationToken(ptyOutput, token, promptTaskText);
|
|
47244
47306
|
if (!verificationPassed) {
|
|
47245
47307
|
this.log(`[${step.name}] Agent "${agent.name}" went idle but verification not yet passed \u2014 waiting for more output`);
|
|
47246
47308
|
const idleGraceSecs = 15;
|
|
@@ -47389,14 +47451,7 @@ DO NOT:
|
|
|
47389
47451
|
switch (check2.type) {
|
|
47390
47452
|
case "output_contains": {
|
|
47391
47453
|
const token = check2.value;
|
|
47392
|
-
|
|
47393
|
-
if (taskHasToken) {
|
|
47394
|
-
const first = output.indexOf(token);
|
|
47395
|
-
const hasSecond = first !== -1 && output.includes(token, first + token.length);
|
|
47396
|
-
if (!hasSecond) {
|
|
47397
|
-
return fail(`Verification failed for "${stepName}": output does not contain "${token}" (token found only in task injection \u2014 agent must output it explicitly)`);
|
|
47398
|
-
}
|
|
47399
|
-
} else if (!output.includes(token)) {
|
|
47454
|
+
if (!this.outputContainsVerificationToken(output, token, injectedTaskText)) {
|
|
47400
47455
|
return fail(`Verification failed for "${stepName}": output does not contain "${token}"`);
|
|
47401
47456
|
}
|
|
47402
47457
|
break;
|
|
@@ -47899,8 +47954,15 @@ ${excerpt}` : "";
|
|
|
47899
47954
|
sanitizeChannelName(name) {
|
|
47900
47955
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 32);
|
|
47901
47956
|
}
|
|
47957
|
+
/** Validate that a runId is safe for use in file paths (no traversal). */
|
|
47958
|
+
validateRunId(runId) {
|
|
47959
|
+
if (/[/\\]|^\.\.?$/.test(runId) || runId.includes("..")) {
|
|
47960
|
+
throw new Error(`Invalid runId: "${runId}" contains path traversal characters`);
|
|
47961
|
+
}
|
|
47962
|
+
}
|
|
47902
47963
|
/** Directory for persisted step outputs: .agent-relay/step-outputs/{runId}/ */
|
|
47903
47964
|
getStepOutputDir(runId) {
|
|
47965
|
+
this.validateRunId(runId);
|
|
47904
47966
|
return import_node_path12.default.join(this.cwd, ".agent-relay", "step-outputs", runId);
|
|
47905
47967
|
}
|
|
47906
47968
|
/** Persist step output to disk and post full output as a channel message. */
|
|
@@ -47978,6 +48040,109 @@ ${preview}
|
|
|
47978
48040
|
return void 0;
|
|
47979
48041
|
}
|
|
47980
48042
|
}
|
|
48043
|
+
/** Match the best workflow from config given a set of cached step names. */
|
|
48044
|
+
matchWorkflowFromCache(workflows, cachedStepNames) {
|
|
48045
|
+
if (workflows.length === 1)
|
|
48046
|
+
return workflows[0];
|
|
48047
|
+
if (cachedStepNames.size === 0) {
|
|
48048
|
+
this.log("[resume] Multiple workflows in config with empty cache \u2014 cannot disambiguate");
|
|
48049
|
+
return null;
|
|
48050
|
+
}
|
|
48051
|
+
const scored = workflows.map((candidate) => ({
|
|
48052
|
+
workflow: candidate,
|
|
48053
|
+
matchedSteps: candidate.steps.filter((step) => cachedStepNames.has(step.name)).length,
|
|
48054
|
+
unknownSteps: [...cachedStepNames].filter((name) => !candidate.steps.some((step) => step.name === name)).length
|
|
48055
|
+
})).filter((candidate) => candidate.unknownSteps === 0).sort((a, b) => b.matchedSteps - a.matchedSteps);
|
|
48056
|
+
return scored[0]?.workflow ?? null;
|
|
48057
|
+
}
|
|
48058
|
+
reconstructRunFromCache(runId, config2) {
|
|
48059
|
+
const stepOutputDir = this.getStepOutputDir(runId);
|
|
48060
|
+
if (!(0, import_node_fs9.existsSync)(stepOutputDir))
|
|
48061
|
+
return null;
|
|
48062
|
+
let resumeConfig = config2 ?? this.currentConfig;
|
|
48063
|
+
if (!resumeConfig) {
|
|
48064
|
+
const yamlPath = import_node_path12.default.join(this.cwd, "relay.yaml");
|
|
48065
|
+
if ((0, import_node_fs9.existsSync)(yamlPath)) {
|
|
48066
|
+
try {
|
|
48067
|
+
const raw = (0, import_node_fs9.readFileSync)(yamlPath, "utf-8");
|
|
48068
|
+
resumeConfig = this.parseYamlString(raw, yamlPath);
|
|
48069
|
+
} catch {
|
|
48070
|
+
return null;
|
|
48071
|
+
}
|
|
48072
|
+
} else {
|
|
48073
|
+
return null;
|
|
48074
|
+
}
|
|
48075
|
+
}
|
|
48076
|
+
let entries;
|
|
48077
|
+
try {
|
|
48078
|
+
entries = (0, import_node_fs9.readdirSync)(stepOutputDir, { withFileTypes: true });
|
|
48079
|
+
} catch {
|
|
48080
|
+
return null;
|
|
48081
|
+
}
|
|
48082
|
+
const cachedStepNames = new Set(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3)).filter(Boolean));
|
|
48083
|
+
const workflows = resumeConfig.workflows ?? [];
|
|
48084
|
+
if (workflows.length === 0)
|
|
48085
|
+
return null;
|
|
48086
|
+
const workflow2 = this.matchWorkflowFromCache(workflows, cachedStepNames);
|
|
48087
|
+
if (!workflow2)
|
|
48088
|
+
return null;
|
|
48089
|
+
const stepMtimes = /* @__PURE__ */ new Map();
|
|
48090
|
+
let earliestMtime = Date.now();
|
|
48091
|
+
for (const stepName of cachedStepNames) {
|
|
48092
|
+
try {
|
|
48093
|
+
const mdPath = import_node_path12.default.join(stepOutputDir, `${stepName}.md`);
|
|
48094
|
+
const reportPath = import_node_path12.default.join(stepOutputDir, `${stepName}.report.json`);
|
|
48095
|
+
const mdStat = (0, import_node_fs9.existsSync)(mdPath) ? (0, import_node_fs9.statSync)(mdPath) : null;
|
|
48096
|
+
const reportStat = (0, import_node_fs9.existsSync)(reportPath) ? (0, import_node_fs9.statSync)(reportPath) : null;
|
|
48097
|
+
const mtime = Math.max(mdStat?.mtimeMs ?? 0, reportStat?.mtimeMs ?? 0);
|
|
48098
|
+
if (mtime > 0) {
|
|
48099
|
+
stepMtimes.set(stepName, new Date(mtime).toISOString());
|
|
48100
|
+
if (mtime < earliestMtime)
|
|
48101
|
+
earliestMtime = mtime;
|
|
48102
|
+
}
|
|
48103
|
+
} catch {
|
|
48104
|
+
}
|
|
48105
|
+
}
|
|
48106
|
+
const fallbackTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
48107
|
+
const completedSteps = new Set(workflow2.steps.filter((step) => cachedStepNames.has(step.name)).map((step) => step.name));
|
|
48108
|
+
const failedStepName = workflow2.steps.find((step) => !completedSteps.has(step.name) && (step.dependsOn ?? []).every((dep) => completedSteps.has(dep)))?.name;
|
|
48109
|
+
const runStartedAt = new Date(earliestMtime).toISOString();
|
|
48110
|
+
const run = {
|
|
48111
|
+
id: runId,
|
|
48112
|
+
workspaceId: this.workspaceId,
|
|
48113
|
+
workflowName: workflow2.name,
|
|
48114
|
+
pattern: resumeConfig.swarm.pattern,
|
|
48115
|
+
status: "failed",
|
|
48116
|
+
config: resumeConfig,
|
|
48117
|
+
startedAt: runStartedAt,
|
|
48118
|
+
createdAt: runStartedAt,
|
|
48119
|
+
updatedAt: fallbackTime
|
|
48120
|
+
};
|
|
48121
|
+
const stepStates = /* @__PURE__ */ new Map();
|
|
48122
|
+
for (const step of workflow2.steps) {
|
|
48123
|
+
const isNonAgent = step.type === "deterministic" || step.type === "worktree" || step.type === "integration";
|
|
48124
|
+
const cachedOutput = completedSteps.has(step.name) ? this.loadStepOutput(runId, step.name) : void 0;
|
|
48125
|
+
const status = completedSteps.has(step.name) ? "completed" : step.name === failedStepName ? "failed" : "pending";
|
|
48126
|
+
const stepRow = {
|
|
48127
|
+
id: this.generateId(),
|
|
48128
|
+
runId,
|
|
48129
|
+
stepName: step.name,
|
|
48130
|
+
agentName: isNonAgent ? null : step.agent ?? null,
|
|
48131
|
+
stepType: isNonAgent ? step.type : "agent",
|
|
48132
|
+
status,
|
|
48133
|
+
task: step.type === "deterministic" ? step.command ?? "" : step.type === "worktree" ? step.branch ?? "" : step.type === "integration" ? `${step.integration}.${step.action}` : step.task ?? "",
|
|
48134
|
+
dependsOn: step.dependsOn ?? [],
|
|
48135
|
+
output: cachedOutput,
|
|
48136
|
+
error: status === "failed" ? "Recovered from cached step outputs" : void 0,
|
|
48137
|
+
completedAt: status === "completed" ? stepMtimes.get(step.name) ?? fallbackTime : void 0,
|
|
48138
|
+
retryCount: 0,
|
|
48139
|
+
createdAt: stepMtimes.get(step.name) ?? fallbackTime,
|
|
48140
|
+
updatedAt: stepMtimes.get(step.name) ?? fallbackTime
|
|
48141
|
+
};
|
|
48142
|
+
stepStates.set(step.name, { row: stepRow });
|
|
48143
|
+
}
|
|
48144
|
+
return { run, stepStates };
|
|
48145
|
+
}
|
|
47981
48146
|
/** Get or create the worker logs directory (.agent-relay/team/worker-logs) */
|
|
47982
48147
|
getWorkerLogsDir() {
|
|
47983
48148
|
const logsDir = import_node_path12.default.join(this.cwd, ".agent-relay", "team", "worker-logs");
|
|
@@ -48039,6 +48204,7 @@ var JsonFileWorkflowDb = class {
|
|
|
48039
48204
|
filePath;
|
|
48040
48205
|
/** Whether the storage directory is writable. False = silent no-op mode. */
|
|
48041
48206
|
writable;
|
|
48207
|
+
appendFailedOnce = false;
|
|
48042
48208
|
constructor(filePath) {
|
|
48043
48209
|
this.filePath = filePath;
|
|
48044
48210
|
let writable = false;
|
|
@@ -48053,13 +48219,25 @@ var JsonFileWorkflowDb = class {
|
|
|
48053
48219
|
isWritable() {
|
|
48054
48220
|
return this.writable;
|
|
48055
48221
|
}
|
|
48222
|
+
hasStepOutputs(runId) {
|
|
48223
|
+
try {
|
|
48224
|
+
const dir = import_node_path13.default.join(import_node_path13.default.dirname(this.filePath), "step-outputs", runId);
|
|
48225
|
+
return (0, import_node_fs10.existsSync)(dir) && (0, import_node_fs10.readdirSync)(dir).length > 0;
|
|
48226
|
+
} catch {
|
|
48227
|
+
return false;
|
|
48228
|
+
}
|
|
48229
|
+
}
|
|
48056
48230
|
// ── Private helpers ─────────────────────────────────────────────────────
|
|
48057
48231
|
append(entry) {
|
|
48058
48232
|
if (!this.writable)
|
|
48059
48233
|
return;
|
|
48060
48234
|
try {
|
|
48061
48235
|
(0, import_node_fs10.appendFileSync)(this.filePath, JSON.stringify(entry) + "\n", "utf8");
|
|
48062
|
-
} catch {
|
|
48236
|
+
} catch (err) {
|
|
48237
|
+
if (!this.appendFailedOnce) {
|
|
48238
|
+
this.appendFailedOnce = true;
|
|
48239
|
+
console.warn("[workflow] warning: failed to write run state to " + this.filePath + " \u2014 --resume will not be available for this run. Use --start-from instead. Error: " + (err instanceof Error ? err.message : String(err)));
|
|
48240
|
+
}
|
|
48063
48241
|
}
|
|
48064
48242
|
}
|
|
48065
48243
|
/** Read all lines and build the latest snapshot for each ID. */
|
|
@@ -50487,13 +50665,13 @@ function getRepoFullNameFromPath(workingDirectory) {
|
|
|
50487
50665
|
var import_node_fs14 = __toESM(require("node:fs"), 1);
|
|
50488
50666
|
var import_node_path16 = __toESM(require("node:path"), 1);
|
|
50489
50667
|
var import_node_https = __toESM(require("node:https"), 1);
|
|
50490
|
-
var
|
|
50668
|
+
var import_node_os10 = __toESM(require("node:os"), 1);
|
|
50491
50669
|
var import_compare_versions = __toESM(require_umd(), 1);
|
|
50492
50670
|
var PACKAGE_NAME = "agent-relay";
|
|
50493
50671
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
50494
50672
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
50495
50673
|
function getCachePath() {
|
|
50496
|
-
const cacheDir = import_node_path16.default.join(
|
|
50674
|
+
const cacheDir = import_node_path16.default.join(import_node_os10.default.homedir(), ".agent-relay");
|
|
50497
50675
|
return import_node_path16.default.join(cacheDir, "update-cache.json");
|
|
50498
50676
|
}
|
|
50499
50677
|
function readCache() {
|
|
@@ -50794,7 +50972,7 @@ function validateModelForCli(cli, model) {
|
|
|
50794
50972
|
|
|
50795
50973
|
// packages/utils/dist/relay-pty-path.js
|
|
50796
50974
|
var import_node_fs15 = __toESM(require("node:fs"), 1);
|
|
50797
|
-
var
|
|
50975
|
+
var import_node_os11 = __toESM(require("node:os"), 1);
|
|
50798
50976
|
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
50799
50977
|
var SUPPORTED_PLATFORMS = {
|
|
50800
50978
|
darwin: {
|
|
@@ -50807,13 +50985,13 @@ var SUPPORTED_PLATFORMS = {
|
|
|
50807
50985
|
}
|
|
50808
50986
|
};
|
|
50809
50987
|
function getPlatformBinaryName() {
|
|
50810
|
-
const platform2 =
|
|
50811
|
-
const arch =
|
|
50988
|
+
const platform2 = import_node_os11.default.platform();
|
|
50989
|
+
const arch = import_node_os11.default.arch();
|
|
50812
50990
|
return SUPPORTED_PLATFORMS[platform2]?.[arch] ?? null;
|
|
50813
50991
|
}
|
|
50814
50992
|
function isPlatformSupported() {
|
|
50815
|
-
const platform2 =
|
|
50816
|
-
const arch =
|
|
50993
|
+
const platform2 = import_node_os11.default.platform();
|
|
50994
|
+
const arch = import_node_os11.default.arch();
|
|
50817
50995
|
return SUPPORTED_PLATFORMS[platform2]?.[arch] !== void 0;
|
|
50818
50996
|
}
|
|
50819
50997
|
function getSupportedPlatforms() {
|
|
@@ -50933,7 +51111,7 @@ function isPlatformCompatibleBinary(filePath) {
|
|
|
50933
51111
|
return false;
|
|
50934
51112
|
}
|
|
50935
51113
|
const magic = header.readUInt32BE(0);
|
|
50936
|
-
const platform2 =
|
|
51114
|
+
const platform2 = import_node_os11.default.platform();
|
|
50937
51115
|
if (platform2 === "darwin") {
|
|
50938
51116
|
return isMachOBinary(magic);
|
|
50939
51117
|
}
|
|
@@ -51568,7 +51746,7 @@ var import_node_child_process7 = require("node:child_process");
|
|
|
51568
51746
|
var import_node_crypto13 = __toESM(require("node:crypto"), 1);
|
|
51569
51747
|
var import_node_path18 = __toESM(require("node:path"), 1);
|
|
51570
51748
|
var import_node_fs16 = __toESM(require("node:fs"), 1);
|
|
51571
|
-
var
|
|
51749
|
+
var import_node_os12 = __toESM(require("node:os"), 1);
|
|
51572
51750
|
function getGlobalBaseDir2() {
|
|
51573
51751
|
if (process.env.AGENT_RELAY_DATA_DIR) {
|
|
51574
51752
|
return process.env.AGENT_RELAY_DATA_DIR;
|
|
@@ -51577,7 +51755,7 @@ function getGlobalBaseDir2() {
|
|
|
51577
51755
|
if (xdgDataHome) {
|
|
51578
51756
|
return import_node_path18.default.join(xdgDataHome, "agent-relay");
|
|
51579
51757
|
}
|
|
51580
|
-
return import_node_path18.default.join(
|
|
51758
|
+
return import_node_path18.default.join(import_node_os12.default.homedir(), ".agent-relay");
|
|
51581
51759
|
}
|
|
51582
51760
|
var GLOBAL_BASE_DIR2 = getGlobalBaseDir2();
|
|
51583
51761
|
var PROJECT_DATA_DIR2 = ".agent-relay";
|
|
@@ -51620,10 +51798,10 @@ function getProjectPaths2(projectRoot) {
|
|
|
51620
51798
|
// packages/config/dist/trajectory-config.js
|
|
51621
51799
|
var import_node_fs17 = require("node:fs");
|
|
51622
51800
|
var import_node_path19 = require("node:path");
|
|
51623
|
-
var
|
|
51801
|
+
var import_node_os13 = require("node:os");
|
|
51624
51802
|
var import_node_crypto14 = require("node:crypto");
|
|
51625
51803
|
function getAgentRelayConfigDir() {
|
|
51626
|
-
return process.env.AGENT_RELAY_CONFIG_DIR ?? (0, import_node_path19.join)((0,
|
|
51804
|
+
return process.env.AGENT_RELAY_CONFIG_DIR ?? (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".config", "agent-relay");
|
|
51627
51805
|
}
|
|
51628
51806
|
var configCache = null;
|
|
51629
51807
|
function getRelayConfigPath(_projectRoot) {
|
|
@@ -51667,7 +51845,7 @@ function getProjectHash(projectRoot) {
|
|
|
51667
51845
|
}
|
|
51668
51846
|
function getUserTrajectoriesDir(projectRoot) {
|
|
51669
51847
|
const projectHash = getProjectHash(projectRoot);
|
|
51670
|
-
const configDir = process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0,
|
|
51848
|
+
const configDir = process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".config");
|
|
51671
51849
|
return (0, import_node_path19.join)(configDir, "agent-relay", "trajectories", projectHash);
|
|
51672
51850
|
}
|
|
51673
51851
|
function getRepoTrajectoriesDir(projectRoot) {
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { type CloudApiClient, type CloudAgent } from '../lib/cloud-client.js';
|
|
3
2
|
type ExitFn = (code: number) => never;
|
|
4
|
-
export type { CloudApiClient, CloudAgent };
|
|
5
3
|
export interface CloudDependencies {
|
|
6
|
-
createApiClient: () => CloudApiClient;
|
|
7
|
-
getDataDir: () => string;
|
|
8
|
-
getHostname: () => string;
|
|
9
|
-
randomHex: (bytes: number) => string;
|
|
10
|
-
now: () => Date;
|
|
11
|
-
openExternal: (url: string) => Promise<void>;
|
|
12
|
-
prompt: (question: string) => Promise<string>;
|
|
13
4
|
log: (...args: unknown[]) => void;
|
|
14
5
|
error: (...args: unknown[]) => void;
|
|
15
6
|
exit: ExitFn;
|
|
16
7
|
}
|
|
17
8
|
export declare function registerCloudCommands(program: Command, overrides?: Partial<CloudDependencies>): void;
|
|
9
|
+
export {};
|
|
18
10
|
//# sourceMappingURL=cloud.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/cloud.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/cloud.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAwB1D,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;AAEtC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAyFD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACzC,IAAI,CAsYN"}
|