@lobehub/cli 0.0.60 → 0.0.61
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/index.js +1443 -356
- package/man/man1/lh.1 +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs$2 from "node:fs";
|
|
4
|
-
import fs, { appendFileSync, closeSync, constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync
|
|
4
|
+
import fs, { appendFileSync, closeSync, constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlink, writeFileSync } from "node:fs";
|
|
5
5
|
import * as os$1 from "node:os";
|
|
6
6
|
import os, { EOL, constants as constants$1, homedir, platform, tmpdir } from "node:os";
|
|
7
7
|
import * as path$1 from "node:path";
|
|
@@ -18934,7 +18934,7 @@ var init_heteroSelectorCapabilities = __esmMin((() => {
|
|
|
18934
18934
|
kind: "config"
|
|
18935
18935
|
}],
|
|
18936
18936
|
resolve: resolveCodexModel,
|
|
18937
|
-
source: "
|
|
18937
|
+
source: "catalog"
|
|
18938
18938
|
},
|
|
18939
18939
|
speed: {
|
|
18940
18940
|
encodings: [{
|
|
@@ -19394,6 +19394,53 @@ var init_agent = __esmMin((() => {
|
|
|
19394
19394
|
//#region ../../packages/types/src/agentAccount.ts
|
|
19395
19395
|
var init_agentAccount = __esmMin((() => {}));
|
|
19396
19396
|
|
|
19397
|
+
//#endregion
|
|
19398
|
+
//#region ../../packages/types/src/device.ts
|
|
19399
|
+
var gitLinkedPullRequestSchema, workingDirConfigSchema;
|
|
19400
|
+
var init_device = __esmMin((() => {
|
|
19401
|
+
init_zod$1();
|
|
19402
|
+
gitLinkedPullRequestSchema = object$2({
|
|
19403
|
+
ciStatus: _enum([
|
|
19404
|
+
"failure",
|
|
19405
|
+
"pending",
|
|
19406
|
+
"success",
|
|
19407
|
+
"unknown"
|
|
19408
|
+
]).optional(),
|
|
19409
|
+
isDraft: boolean().optional(),
|
|
19410
|
+
mergeable: string().optional(),
|
|
19411
|
+
mergeStateStatus: string().optional(),
|
|
19412
|
+
mergedAt: string().nullable().optional(),
|
|
19413
|
+
number: number(),
|
|
19414
|
+
reviewDecision: string().optional(),
|
|
19415
|
+
state: string(),
|
|
19416
|
+
title: string(),
|
|
19417
|
+
url: string()
|
|
19418
|
+
});
|
|
19419
|
+
workingDirConfigSchema = object$2({
|
|
19420
|
+
git: object$2({
|
|
19421
|
+
activeWorktree: string().optional(),
|
|
19422
|
+
branch: string().optional(),
|
|
19423
|
+
detached: boolean().optional(),
|
|
19424
|
+
github: object$2({
|
|
19425
|
+
extraPullRequestCount: number().optional(),
|
|
19426
|
+
pullRequest: gitLinkedPullRequestSchema.nullable().optional(),
|
|
19427
|
+
pullRequestStatus: _enum([
|
|
19428
|
+
"error",
|
|
19429
|
+
"gh-missing",
|
|
19430
|
+
"ok"
|
|
19431
|
+
]).optional()
|
|
19432
|
+
}).optional(),
|
|
19433
|
+
isWorktree: boolean().optional(),
|
|
19434
|
+
upstream: object$2({
|
|
19435
|
+
branch: string(),
|
|
19436
|
+
remote: string()
|
|
19437
|
+
}).optional()
|
|
19438
|
+
}).optional(),
|
|
19439
|
+
path: string(),
|
|
19440
|
+
repoType: _enum(["git", "github"]).optional()
|
|
19441
|
+
});
|
|
19442
|
+
}));
|
|
19443
|
+
|
|
19397
19444
|
//#endregion
|
|
19398
19445
|
//#region ../../packages/types/src/agentExecution/credentialFacts.ts
|
|
19399
19446
|
var init_credentialFacts = __esmMin((() => {}));
|
|
@@ -19404,9 +19451,26 @@ var init_modelFacts = __esmMin((() => {}));
|
|
|
19404
19451
|
|
|
19405
19452
|
//#endregion
|
|
19406
19453
|
//#region ../../packages/types/src/agentExecution/index.ts
|
|
19454
|
+
var initialTopicMetadataSchema;
|
|
19407
19455
|
var init_agentExecution = __esmMin((() => {
|
|
19456
|
+
init_zod$1();
|
|
19457
|
+
init_device();
|
|
19408
19458
|
init_credentialFacts();
|
|
19409
19459
|
init_modelFacts();
|
|
19460
|
+
initialTopicMetadataSchema = object$2({
|
|
19461
|
+
repos: array(string()).optional(),
|
|
19462
|
+
/**
|
|
19463
|
+
* Cloud-sandbox instance the composer chose before any topic existed. The
|
|
19464
|
+
* choice is made on a conversation that has nothing to write to yet, so it
|
|
19465
|
+
* travels with the first send instead — the server cannot read the client's
|
|
19466
|
+
* pending selection, and without this the new topic would be born unbound and
|
|
19467
|
+
* silently run at the workspace root.
|
|
19468
|
+
*/
|
|
19469
|
+
sandboxInstanceId: string().optional(),
|
|
19470
|
+
sandboxMode: _enum(["ephemeral", "persistent"]).optional(),
|
|
19471
|
+
workingDirectory: string().optional(),
|
|
19472
|
+
workingDirectoryConfig: workingDirConfigSchema.optional()
|
|
19473
|
+
});
|
|
19410
19474
|
}));
|
|
19411
19475
|
|
|
19412
19476
|
//#endregion
|
|
@@ -20637,55 +20701,12 @@ var init_conversation = __esmMin((() => {}));
|
|
|
20637
20701
|
var init_creds = __esmMin((() => {}));
|
|
20638
20702
|
|
|
20639
20703
|
//#endregion
|
|
20640
|
-
//#region ../../packages/types/src/
|
|
20641
|
-
var
|
|
20704
|
+
//#region ../../packages/types/src/dashboard/index.ts
|
|
20705
|
+
var init_dashboard = __esmMin((() => {}));
|
|
20642
20706
|
|
|
20643
20707
|
//#endregion
|
|
20644
|
-
//#region ../../packages/types/src/
|
|
20645
|
-
var
|
|
20646
|
-
var init_device = __esmMin((() => {
|
|
20647
|
-
init_zod$1();
|
|
20648
|
-
gitLinkedPullRequestSchema = object$2({
|
|
20649
|
-
ciStatus: _enum([
|
|
20650
|
-
"failure",
|
|
20651
|
-
"pending",
|
|
20652
|
-
"success",
|
|
20653
|
-
"unknown"
|
|
20654
|
-
]).optional(),
|
|
20655
|
-
isDraft: boolean().optional(),
|
|
20656
|
-
mergeable: string().optional(),
|
|
20657
|
-
mergeStateStatus: string().optional(),
|
|
20658
|
-
mergedAt: string().nullable().optional(),
|
|
20659
|
-
number: number(),
|
|
20660
|
-
reviewDecision: string().optional(),
|
|
20661
|
-
state: string(),
|
|
20662
|
-
title: string(),
|
|
20663
|
-
url: string()
|
|
20664
|
-
});
|
|
20665
|
-
workingDirConfigSchema = object$2({
|
|
20666
|
-
git: object$2({
|
|
20667
|
-
activeWorktree: string().optional(),
|
|
20668
|
-
branch: string().optional(),
|
|
20669
|
-
detached: boolean().optional(),
|
|
20670
|
-
github: object$2({
|
|
20671
|
-
extraPullRequestCount: number().optional(),
|
|
20672
|
-
pullRequest: gitLinkedPullRequestSchema.nullable().optional(),
|
|
20673
|
-
pullRequestStatus: _enum([
|
|
20674
|
-
"error",
|
|
20675
|
-
"gh-missing",
|
|
20676
|
-
"ok"
|
|
20677
|
-
]).optional()
|
|
20678
|
-
}).optional(),
|
|
20679
|
-
isWorktree: boolean().optional(),
|
|
20680
|
-
upstream: object$2({
|
|
20681
|
-
branch: string(),
|
|
20682
|
-
remote: string()
|
|
20683
|
-
}).optional()
|
|
20684
|
-
}).optional(),
|
|
20685
|
-
path: string(),
|
|
20686
|
-
repoType: _enum(["git", "github"]).optional()
|
|
20687
|
-
});
|
|
20688
|
-
}));
|
|
20708
|
+
//#region ../../packages/types/src/dbMigration.ts
|
|
20709
|
+
var init_dbMigration = __esmMin((() => {}));
|
|
20689
20710
|
|
|
20690
20711
|
//#endregion
|
|
20691
20712
|
//#region ../../packages/types/src/deviceMetric.ts
|
|
@@ -21170,22 +21191,43 @@ var init_goal = __esmMin((() => {}));
|
|
|
21170
21191
|
|
|
21171
21192
|
//#endregion
|
|
21172
21193
|
//#region ../../packages/types/src/goalReport.ts
|
|
21173
|
-
var idList, goalReportDetourKinds, GoalReportDetourSchema, GoalReportChapterSchema, GoalReportNextStepSchema, GoalReportMainlineSchema, GoalReportMetadataSchema;
|
|
21194
|
+
var idList, NO_VALUE_TOKENS, optionalId, goalReportDetourKinds, GOAL_REPORT_MAX_DETOUR_TITLE_LENGTH, GOAL_REPORT_MAX_DETOUR_TEXT_LENGTH, GoalReportDetourSchema, GOAL_REPORT_MAX_DETOURS_PER_CHAPTER, GoalReportChapterSchema, GoalReportNextStepSchema, GoalReportMainlineSchema, GoalReportMetadataSchema;
|
|
21174
21195
|
var init_goalReport = __esmMin((() => {
|
|
21175
21196
|
init_zod$1();
|
|
21176
21197
|
idList = array(string().trim().min(1)).max(200);
|
|
21198
|
+
NO_VALUE_TOKENS = new Set([
|
|
21199
|
+
"",
|
|
21200
|
+
"-",
|
|
21201
|
+
"—",
|
|
21202
|
+
"n/a",
|
|
21203
|
+
"na",
|
|
21204
|
+
"nil",
|
|
21205
|
+
"none",
|
|
21206
|
+
"null",
|
|
21207
|
+
"undefined"
|
|
21208
|
+
]);
|
|
21209
|
+
optionalId = preprocess((value) => {
|
|
21210
|
+
if (value === null || value === void 0) return void 0;
|
|
21211
|
+
if (typeof value !== "string") return value;
|
|
21212
|
+
const trimmed = value.trim();
|
|
21213
|
+
if (trimmed === "" || NO_VALUE_TOKENS.has(trimmed.toLowerCase())) return void 0;
|
|
21214
|
+
return trimmed;
|
|
21215
|
+
}, string().trim().min(1).optional());
|
|
21177
21216
|
goalReportDetourKinds = [
|
|
21178
21217
|
"dead_end",
|
|
21179
21218
|
"superseded",
|
|
21180
21219
|
"retry"
|
|
21181
21220
|
];
|
|
21221
|
+
GOAL_REPORT_MAX_DETOUR_TITLE_LENGTH = 200;
|
|
21222
|
+
GOAL_REPORT_MAX_DETOUR_TEXT_LENGTH = 2e3;
|
|
21182
21223
|
GoalReportDetourSchema = object$2({
|
|
21183
21224
|
kind: _enum(goalReportDetourKinds),
|
|
21184
|
-
lesson: string().trim().min(1).max(
|
|
21225
|
+
lesson: string().trim().min(1).max(GOAL_REPORT_MAX_DETOUR_TEXT_LENGTH),
|
|
21185
21226
|
nodeIds: idList.min(1),
|
|
21186
|
-
reason: string().trim().min(1).max(
|
|
21227
|
+
reason: string().trim().min(1).max(GOAL_REPORT_MAX_DETOUR_TEXT_LENGTH),
|
|
21187
21228
|
title: string().trim().min(1).max(200)
|
|
21188
21229
|
}).strict();
|
|
21230
|
+
GOAL_REPORT_MAX_DETOURS_PER_CHAPTER = 20;
|
|
21189
21231
|
GoalReportChapterSchema = object$2({
|
|
21190
21232
|
detours: array(GoalReportDetourSchema).max(20).default([]),
|
|
21191
21233
|
findingIds: idList.default([]),
|
|
@@ -21205,7 +21247,12 @@ var init_goalReport = __esmMin((() => {
|
|
|
21205
21247
|
}).strict();
|
|
21206
21248
|
GoalReportMetadataSchema = object$2({
|
|
21207
21249
|
chapters: array(GoalReportChapterSchema).min(1).max(30),
|
|
21208
|
-
|
|
21250
|
+
/**
|
|
21251
|
+
* The Goal's final deliverable, when it produced one. A placeholder such as
|
|
21252
|
+
* `""` or `"none"` is read as "no deliverable" rather than rejected, so a
|
|
21253
|
+
* Goal without one can still report.
|
|
21254
|
+
*/
|
|
21255
|
+
deliverableWorkId: optionalId,
|
|
21209
21256
|
/** The newest `goal_events.id` the report was written against. */
|
|
21210
21257
|
graphCursor: string().trim().min(1),
|
|
21211
21258
|
headline: string().trim().min(1).max(500),
|
|
@@ -21968,6 +22015,9 @@ var init_topic$1 = __esmMin((() => {
|
|
|
21968
22015
|
messageId: string(),
|
|
21969
22016
|
reservedAt: string()
|
|
21970
22017
|
}).nullable().optional(),
|
|
22018
|
+
sandboxCwd: string().optional(),
|
|
22019
|
+
sandboxInstanceId: string().optional(),
|
|
22020
|
+
sandboxMode: _enum(["ephemeral", "persistent"]).optional(),
|
|
21971
22021
|
scheduledRun: topicScheduledRunSchema.nullish(),
|
|
21972
22022
|
workingDirectory: string().optional(),
|
|
21973
22023
|
workingDirectoryConfig: workingDirConfigSchema.optional()
|
|
@@ -22163,7 +22213,13 @@ var init_preference = __esmMin((() => {
|
|
|
22163
22213
|
* expose a port on the working device through a tunnel link, so a dev server
|
|
22164
22214
|
* running there can be opened from this UI
|
|
22165
22215
|
*/
|
|
22166
|
-
enableDeviceTunnel: boolean().optional()
|
|
22216
|
+
enableDeviceTunnel: boolean().optional(),
|
|
22217
|
+
/**
|
|
22218
|
+
* opt this user's cloud-sandbox runs into a persistent workspace: the working
|
|
22219
|
+
* directory survives session recycling and is shared across their topics,
|
|
22220
|
+
* instead of being thrown away with the sandbox
|
|
22221
|
+
*/
|
|
22222
|
+
enablePersistentSandbox: boolean().optional()
|
|
22167
22223
|
});
|
|
22168
22224
|
GithubIntegrationPreferenceSchema = object$2({
|
|
22169
22225
|
acceptOnMerge: boolean().optional(),
|
|
@@ -22607,6 +22663,10 @@ var init_userMemory = __esmMin((() => {
|
|
|
22607
22663
|
//#region ../../packages/types/src/verify.ts
|
|
22608
22664
|
var init_verify = __esmMin((() => {}));
|
|
22609
22665
|
|
|
22666
|
+
//#endregion
|
|
22667
|
+
//#region ../../packages/types/src/widget/index.ts
|
|
22668
|
+
var init_widget = __esmMin((() => {}));
|
|
22669
|
+
|
|
22610
22670
|
//#endregion
|
|
22611
22671
|
//#region ../../packages/types/src/work.ts
|
|
22612
22672
|
var WORK_PROVIDER_RESOURCE_TYPES, RESOURCE_TYPE_TO_PROVIDER;
|
|
@@ -22723,6 +22783,7 @@ var init_src$1 = __esmMin((() => {
|
|
|
22723
22783
|
init_chunk$1();
|
|
22724
22784
|
init_conversation();
|
|
22725
22785
|
init_creds();
|
|
22786
|
+
init_dashboard();
|
|
22726
22787
|
init_dbMigration();
|
|
22727
22788
|
init_device();
|
|
22728
22789
|
init_deviceMetric();
|
|
@@ -22774,6 +22835,7 @@ var init_src$1 = __esmMin((() => {
|
|
|
22774
22835
|
init_user();
|
|
22775
22836
|
init_userMemory();
|
|
22776
22837
|
init_verify();
|
|
22838
|
+
init_widget();
|
|
22777
22839
|
init_work();
|
|
22778
22840
|
init_acceptanceComment();
|
|
22779
22841
|
init_acceptanceFlow();
|
|
@@ -23127,6 +23189,7 @@ const TRPC_NAMESPACE_API_KEY_RULES = {
|
|
|
23127
23189
|
composio: "blocked",
|
|
23128
23190
|
config: "open",
|
|
23129
23191
|
connector: "blocked",
|
|
23192
|
+
dashboard: rw("agent:read", null),
|
|
23130
23193
|
device: "blocked",
|
|
23131
23194
|
deviceMetric: "blocked",
|
|
23132
23195
|
document: rw("knowledge:read", "knowledge:write"),
|
|
@@ -23167,6 +23230,7 @@ const TRPC_NAMESPACE_API_KEY_RULES = {
|
|
|
23167
23230
|
referral: "blocked",
|
|
23168
23231
|
resourcePermission: "blocked",
|
|
23169
23232
|
resourceTransferRequest: "blocked",
|
|
23233
|
+
sandboxStorage: "blocked",
|
|
23170
23234
|
search: rw("chat:read", null),
|
|
23171
23235
|
scm: "blocked",
|
|
23172
23236
|
session: rw("chat:read", "chat:write"),
|
|
@@ -23192,6 +23256,7 @@ const TRPC_NAMESPACE_API_KEY_RULES = {
|
|
|
23192
23256
|
video: { any: "model:invoke" },
|
|
23193
23257
|
waitlist: "blocked",
|
|
23194
23258
|
webBrowsing: { any: "model:invoke" },
|
|
23259
|
+
widget: rw("agent:read", null),
|
|
23195
23260
|
work: rw("agent:read", "agent:write"),
|
|
23196
23261
|
workspace: rw("workspace:read", "workspace:write"),
|
|
23197
23262
|
workspaceAuditLog: rw("workspace:read", null),
|
|
@@ -25371,33 +25436,199 @@ async function getAgentStreamAuthInfo(workspaceId) {
|
|
|
25371
25436
|
};
|
|
25372
25437
|
}
|
|
25373
25438
|
|
|
25439
|
+
//#endregion
|
|
25440
|
+
//#region src/utils/agentRunOutcome.ts
|
|
25441
|
+
/**
|
|
25442
|
+
* Exit status contract of `lh agent run`. Scripts can tell a parked run apart
|
|
25443
|
+
* from a finished one, and both apart from a run the CLI could not confirm.
|
|
25444
|
+
*/
|
|
25445
|
+
const AGENT_RUN_EXIT_CODES = {
|
|
25446
|
+
completed: 0,
|
|
25447
|
+
failed: 1,
|
|
25448
|
+
interrupted: 1,
|
|
25449
|
+
unknown: 3,
|
|
25450
|
+
waiting_for_human: 2
|
|
25451
|
+
};
|
|
25452
|
+
const COMPLETED_STATUSES = new Set([
|
|
25453
|
+
"completed",
|
|
25454
|
+
"done",
|
|
25455
|
+
"success"
|
|
25456
|
+
]);
|
|
25457
|
+
const FAILED_STATUSES = new Set(["error", "failed"]);
|
|
25458
|
+
const INTERRUPTED_STATUSES = new Set([
|
|
25459
|
+
"aborted",
|
|
25460
|
+
"canceled",
|
|
25461
|
+
"cancelled",
|
|
25462
|
+
"interrupted"
|
|
25463
|
+
]);
|
|
25464
|
+
/** Non-terminal runtime statuses: the run is still going (or about to resume). */
|
|
25465
|
+
const ACTIVE_STATUSES = new Set([
|
|
25466
|
+
"idle",
|
|
25467
|
+
"pending",
|
|
25468
|
+
"processing",
|
|
25469
|
+
"running",
|
|
25470
|
+
"waiting_for_async_tool"
|
|
25471
|
+
]);
|
|
25472
|
+
/**
|
|
25473
|
+
* Classify a runtime status or `agent_runtime_end` reason. Returns `active`
|
|
25474
|
+
* for a run that is still going and `undefined` for a value the CLI does not
|
|
25475
|
+
* recognise.
|
|
25476
|
+
*/
|
|
25477
|
+
const classifyRunStatus = (status) => {
|
|
25478
|
+
if (!status) return void 0;
|
|
25479
|
+
if (COMPLETED_STATUSES.has(status)) return "completed";
|
|
25480
|
+
if (FAILED_STATUSES.has(status)) return "failed";
|
|
25481
|
+
if (INTERRUPTED_STATUSES.has(status)) return "interrupted";
|
|
25482
|
+
if (status === "waiting_for_human") return "waiting_for_human";
|
|
25483
|
+
if (ACTIVE_STATUSES.has(status)) return "active";
|
|
25484
|
+
};
|
|
25485
|
+
const errorText = (error) => {
|
|
25486
|
+
if (!error) return void 0;
|
|
25487
|
+
if (typeof error === "string") return error;
|
|
25488
|
+
if (typeof error === "object" && "message" in error) return String(error.message);
|
|
25489
|
+
return JSON.stringify(error);
|
|
25490
|
+
};
|
|
25491
|
+
/**
|
|
25492
|
+
* Read `aiAgent.getOperationStatus`. The server answers with an envelope —
|
|
25493
|
+
* `{ currentState: { status, stepCount, usage, cost, error }, isCompleted,
|
|
25494
|
+
* hasError, needsHumanInput, isActive, ... }` — so the run's fields live under
|
|
25495
|
+
* `currentState`, not at the top level. The flat reads are kept as fallbacks
|
|
25496
|
+
* for older servers, and the envelope's summary flags stand in when there is
|
|
25497
|
+
* no status string at all.
|
|
25498
|
+
*/
|
|
25499
|
+
const readOperationStatus = (r) => {
|
|
25500
|
+
const state = r?.currentState ?? {};
|
|
25501
|
+
let status = state.status ?? r?.status ?? r?.state;
|
|
25502
|
+
if (!status) {
|
|
25503
|
+
if (r?.isCompleted) status = r.hasError ? "error" : "done";
|
|
25504
|
+
else if (r?.needsHumanInput) status = "waiting_for_human";
|
|
25505
|
+
else if (r?.isActive) status = "running";
|
|
25506
|
+
}
|
|
25507
|
+
return {
|
|
25508
|
+
cost: state.cost ?? r?.cost,
|
|
25509
|
+
error: errorText(state.error ?? r?.error),
|
|
25510
|
+
status,
|
|
25511
|
+
stepCount: state.stepCount ?? r?.stepCount ?? r?.stats?.totalSteps,
|
|
25512
|
+
usage: state.usage ?? r?.usage
|
|
25513
|
+
};
|
|
25514
|
+
};
|
|
25515
|
+
const colorStatus$1 = (status) => {
|
|
25516
|
+
switch (classifyRunStatus(status)) {
|
|
25517
|
+
case "completed": return import_picocolors.default.green(status);
|
|
25518
|
+
case "failed": return import_picocolors.default.red(status);
|
|
25519
|
+
case "interrupted":
|
|
25520
|
+
case "waiting_for_human": return import_picocolors.default.yellow(status);
|
|
25521
|
+
case "active": return import_picocolors.default.cyan(status);
|
|
25522
|
+
default: return import_picocolors.default.dim(status);
|
|
25523
|
+
}
|
|
25524
|
+
};
|
|
25525
|
+
/** One-line human summary of an outcome, used by the stream and poll paths. */
|
|
25526
|
+
const describeOutcome = (outcome) => {
|
|
25527
|
+
switch (outcome.kind) {
|
|
25528
|
+
case "completed": return `${import_picocolors.default.green("✓")} Agent finished`;
|
|
25529
|
+
case "failed": return `${import_picocolors.default.red("✗")} Agent failed${outcome.error ? `: ${outcome.error}` : ""}`;
|
|
25530
|
+
case "interrupted": return `${import_picocolors.default.yellow("■")} Agent interrupted`;
|
|
25531
|
+
case "waiting_for_human": return `${import_picocolors.default.yellow("⏸")} Agent paused: waiting for human approval — approve or answer it in LobeHub, the CLI does not wait for it`;
|
|
25532
|
+
case "unknown": return `${import_picocolors.default.yellow("?")} Agent run outcome unknown${outcome.status ? ` (status: ${outcome.status})` : ""}`;
|
|
25533
|
+
}
|
|
25534
|
+
};
|
|
25535
|
+
|
|
25374
25536
|
//#endregion
|
|
25375
25537
|
//#region src/utils/agentStream.ts
|
|
25376
25538
|
/**
|
|
25539
|
+
* Map an `agent_runtime_end` event to the run outcome. The server ends the
|
|
25540
|
+
* stream on `done`, `error`, `interrupted` and `waiting_for_human` alike, so the
|
|
25541
|
+
* end event alone does not mean the run succeeded — `data.reason` says how.
|
|
25542
|
+
*/
|
|
25543
|
+
const outcomeFromEndEvent = (event) => {
|
|
25544
|
+
const reason = event.data?.reason;
|
|
25545
|
+
const kind = classifyRunStatus(reason);
|
|
25546
|
+
const error = event.data?.finalState?.error;
|
|
25547
|
+
return {
|
|
25548
|
+
error: typeof error === "string" ? error : error?.message ?? void 0,
|
|
25549
|
+
kind: !reason ? "completed" : kind && kind !== "active" ? kind : "unknown",
|
|
25550
|
+
status: reason
|
|
25551
|
+
};
|
|
25552
|
+
};
|
|
25553
|
+
const STALL_TIMEOUT = 6e4;
|
|
25554
|
+
const outcomeFromErrorEvent = (event) => ({
|
|
25555
|
+
error: event.data?.message || event.data?.error || "Unknown error",
|
|
25556
|
+
kind: "failed",
|
|
25557
|
+
status: "error"
|
|
25558
|
+
});
|
|
25559
|
+
/**
|
|
25377
25560
|
* Connect to the agent SSE stream and render events to the terminal.
|
|
25378
|
-
* Resolves
|
|
25561
|
+
* Resolves with the run outcome once a terminal event arrives, or `undefined`
|
|
25562
|
+
* when the stream closed without one (the caller should check the status).
|
|
25563
|
+
* Rejects when the stream cannot be opened — the run may still be executing
|
|
25564
|
+
* server-side, so the caller falls back to polling rather than exiting here.
|
|
25379
25565
|
*/
|
|
25380
25566
|
async function streamAgentEvents(url, headers, options = {}) {
|
|
25381
|
-
const
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25567
|
+
const { onStall, stallTimeoutMs = STALL_TIMEOUT } = options;
|
|
25568
|
+
const jsonEvents = [];
|
|
25569
|
+
let jsonPrinted = false;
|
|
25570
|
+
const printJsonOnce = () => {
|
|
25571
|
+
if (!options.json || jsonPrinted) return;
|
|
25572
|
+
jsonPrinted = true;
|
|
25573
|
+
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25574
|
+
};
|
|
25575
|
+
let res;
|
|
25576
|
+
try {
|
|
25577
|
+
res = await fetch(url, { headers });
|
|
25578
|
+
if (!res.ok) {
|
|
25579
|
+
const text = await res.text();
|
|
25580
|
+
throw new Error(`Agent stream failed: ${res.status} ${text}`);
|
|
25581
|
+
}
|
|
25582
|
+
if (!res.body) throw new Error("No response body received from agent stream");
|
|
25583
|
+
} catch (error) {
|
|
25584
|
+
printJsonOnce();
|
|
25585
|
+
throw error;
|
|
25390
25586
|
}
|
|
25391
25587
|
const reader = res.body.getReader();
|
|
25392
25588
|
const decoder = new TextDecoder();
|
|
25393
25589
|
let buffer = "";
|
|
25394
|
-
const jsonEvents = [];
|
|
25395
25590
|
const ctx = createRenderContext();
|
|
25396
25591
|
let eventType = "";
|
|
25397
25592
|
let eventData = "";
|
|
25593
|
+
let finished = false;
|
|
25594
|
+
let stallTimer;
|
|
25595
|
+
let stalled;
|
|
25596
|
+
const armStallTimer = () => {
|
|
25597
|
+
if (!onStall) return;
|
|
25598
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
25599
|
+
stallTimer = setTimeout(async () => {
|
|
25600
|
+
if (finished) return;
|
|
25601
|
+
const silence = `Agent stream sent no progress for ${Math.round(stallTimeoutMs / 1e3)}s`;
|
|
25602
|
+
log$13.debug(`${silence}; checking the run status`);
|
|
25603
|
+
try {
|
|
25604
|
+
const outcome = await onStall();
|
|
25605
|
+
if (finished) return;
|
|
25606
|
+
if (!outcome) {
|
|
25607
|
+
armStallTimer();
|
|
25608
|
+
return;
|
|
25609
|
+
}
|
|
25610
|
+
stalled = { outcome };
|
|
25611
|
+
} catch (error) {
|
|
25612
|
+
if (finished) return;
|
|
25613
|
+
stalled = { error: /* @__PURE__ */ new Error(`${silence}; status check failed: ${error.message}`) };
|
|
25614
|
+
}
|
|
25615
|
+
reader.cancel();
|
|
25616
|
+
}, stallTimeoutMs);
|
|
25617
|
+
};
|
|
25618
|
+
armStallTimer();
|
|
25398
25619
|
try {
|
|
25399
25620
|
while (true) {
|
|
25400
25621
|
const { done, value } = await reader.read();
|
|
25622
|
+
if (stalled) {
|
|
25623
|
+
finished = true;
|
|
25624
|
+
if ("error" in stalled) {
|
|
25625
|
+
printJsonOnce();
|
|
25626
|
+
throw stalled.error;
|
|
25627
|
+
}
|
|
25628
|
+
if (options.json) printJsonOnce();
|
|
25629
|
+
else renderOutcome(stalled.outcome);
|
|
25630
|
+
return stalled.outcome;
|
|
25631
|
+
}
|
|
25401
25632
|
if (done) break;
|
|
25402
25633
|
buffer += decoder.decode(value, { stream: true });
|
|
25403
25634
|
const lines = buffer.split("\n");
|
|
@@ -25415,19 +25646,22 @@ async function streamAgentEvents(url, headers, options = {}) {
|
|
|
25415
25646
|
eventData = "";
|
|
25416
25647
|
continue;
|
|
25417
25648
|
}
|
|
25649
|
+
armStallTimer();
|
|
25418
25650
|
try {
|
|
25419
25651
|
const event = JSON.parse(eventData);
|
|
25420
25652
|
if (options.json) jsonEvents.push(event);
|
|
25421
25653
|
else renderEvent(event, ctx, options);
|
|
25422
25654
|
if (event.type === "agent_runtime_end") {
|
|
25423
|
-
|
|
25424
|
-
|
|
25425
|
-
|
|
25655
|
+
const outcome = outcomeFromEndEvent(event);
|
|
25656
|
+
if (options.json) printJsonOnce();
|
|
25657
|
+
else renderEnd(event, outcome);
|
|
25658
|
+
return outcome;
|
|
25426
25659
|
}
|
|
25427
25660
|
if (event.type === "error") {
|
|
25428
|
-
|
|
25429
|
-
|
|
25430
|
-
|
|
25661
|
+
printJsonOnce();
|
|
25662
|
+
const outcome = outcomeFromErrorEvent(event);
|
|
25663
|
+
log$13.error(`Agent error: ${outcome.error}`);
|
|
25664
|
+
return outcome;
|
|
25431
25665
|
}
|
|
25432
25666
|
} catch {}
|
|
25433
25667
|
eventType = "";
|
|
@@ -25435,18 +25669,26 @@ async function streamAgentEvents(url, headers, options = {}) {
|
|
|
25435
25669
|
}
|
|
25436
25670
|
}
|
|
25437
25671
|
}
|
|
25438
|
-
|
|
25672
|
+
printJsonOnce();
|
|
25673
|
+
return;
|
|
25439
25674
|
} finally {
|
|
25675
|
+
finished = true;
|
|
25676
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
25440
25677
|
reader.releaseLock();
|
|
25441
25678
|
}
|
|
25442
25679
|
}
|
|
25443
25680
|
/**
|
|
25444
25681
|
* Replay previously saved JSON events (from --json output) to the terminal.
|
|
25445
|
-
* No network calls needed.
|
|
25682
|
+
* No network calls needed. Returns the outcome carried by the recorded terminal
|
|
25683
|
+
* event, or `undefined` when the recording has none (e.g. a truncated capture).
|
|
25446
25684
|
*/
|
|
25447
25685
|
function replayAgentEvents(events, options = {}) {
|
|
25448
25686
|
if (options.json) {
|
|
25449
25687
|
console.log(JSON.stringify(events, null, 2));
|
|
25688
|
+
for (const event of events) {
|
|
25689
|
+
if (event.type === "agent_runtime_end") return outcomeFromEndEvent(event);
|
|
25690
|
+
if (event.type === "error") return outcomeFromErrorEvent(event);
|
|
25691
|
+
}
|
|
25450
25692
|
return;
|
|
25451
25693
|
}
|
|
25452
25694
|
const ctx = createRenderContext();
|
|
@@ -25454,22 +25696,26 @@ function replayAgentEvents(events, options = {}) {
|
|
|
25454
25696
|
if (!event.type) continue;
|
|
25455
25697
|
renderEvent(event, ctx, options);
|
|
25456
25698
|
if (event.type === "agent_runtime_end") {
|
|
25457
|
-
|
|
25458
|
-
|
|
25699
|
+
const outcome = outcomeFromEndEvent(event);
|
|
25700
|
+
renderEnd(event, outcome);
|
|
25701
|
+
return outcome;
|
|
25459
25702
|
}
|
|
25460
25703
|
if (event.type === "error") {
|
|
25461
|
-
|
|
25462
|
-
|
|
25704
|
+
const outcome = outcomeFromErrorEvent(event);
|
|
25705
|
+
log$13.error(`Agent error: ${outcome.error}`);
|
|
25706
|
+
return outcome;
|
|
25463
25707
|
}
|
|
25464
25708
|
}
|
|
25465
25709
|
}
|
|
25466
25710
|
const HEARTBEAT_INTERVAL$1 = 3e4;
|
|
25467
25711
|
/**
|
|
25468
25712
|
* Connect to the Agent Gateway via WebSocket and render events to the terminal.
|
|
25469
|
-
* Resolves
|
|
25713
|
+
* Resolves with the run outcome once a terminal event arrives, `undefined` when
|
|
25714
|
+
* the gateway completed the session without one, and rejects when the
|
|
25715
|
+
* connection fails so the caller can fall back to polling.
|
|
25470
25716
|
*/
|
|
25471
25717
|
async function streamAgentEventsViaWebSocket(options) {
|
|
25472
|
-
const { gatewayUrl, operationId, serverUrl, token, tokenType = "jwt", ...streamOpts } = options;
|
|
25718
|
+
const { gatewayUrl, onStall, operationId, serverUrl, stallTimeoutMs = STALL_TIMEOUT, token, tokenType = "jwt", ...streamOpts } = options;
|
|
25473
25719
|
const wsUrl = urlJoin(gatewayUrl.replace(/^http/, "ws"), `/ws?operationId=${encodeURIComponent(operationId)}`);
|
|
25474
25720
|
log$13.debug(`Connecting to gateway: ${wsUrl} (auth: ${tokenType})`);
|
|
25475
25721
|
return new Promise((resolve, reject) => {
|
|
@@ -25477,12 +25723,56 @@ async function streamAgentEventsViaWebSocket(options) {
|
|
|
25477
25723
|
const jsonEvents = [];
|
|
25478
25724
|
const ctx = createRenderContext();
|
|
25479
25725
|
let heartbeatTimer;
|
|
25726
|
+
let stallTimer;
|
|
25480
25727
|
let isSettled = false;
|
|
25481
25728
|
let jsonPrinted = false;
|
|
25482
25729
|
const cleanup = () => {
|
|
25483
25730
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
25731
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
25484
25732
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close();
|
|
25485
25733
|
};
|
|
25734
|
+
const printJsonOnce = () => {
|
|
25735
|
+
if (streamOpts.json && !jsonPrinted) {
|
|
25736
|
+
jsonPrinted = true;
|
|
25737
|
+
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25738
|
+
}
|
|
25739
|
+
};
|
|
25740
|
+
const settle = (outcome) => {
|
|
25741
|
+
if (isSettled) return;
|
|
25742
|
+
isSettled = true;
|
|
25743
|
+
cleanup();
|
|
25744
|
+
resolve(outcome);
|
|
25745
|
+
};
|
|
25746
|
+
const fail = (error) => {
|
|
25747
|
+
if (isSettled) return;
|
|
25748
|
+
isSettled = true;
|
|
25749
|
+
cleanup();
|
|
25750
|
+
printJsonOnce();
|
|
25751
|
+
reject(error);
|
|
25752
|
+
};
|
|
25753
|
+
const armStallTimer = () => {
|
|
25754
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
25755
|
+
stallTimer = setTimeout(async () => {
|
|
25756
|
+
if (isSettled) return;
|
|
25757
|
+
const silence = `Agent gateway WebSocket sent no progress for ${Math.round(stallTimeoutMs / 1e3)}s`;
|
|
25758
|
+
if (!onStall) {
|
|
25759
|
+
fail(/* @__PURE__ */ new Error(`${silence} and never reported completion`));
|
|
25760
|
+
return;
|
|
25761
|
+
}
|
|
25762
|
+
log$13.debug(`${silence}; checking the run status`);
|
|
25763
|
+
try {
|
|
25764
|
+
const outcome = await onStall();
|
|
25765
|
+
if (isSettled) return;
|
|
25766
|
+
if (outcome) {
|
|
25767
|
+
if (!streamOpts.json) renderOutcome(outcome);
|
|
25768
|
+
printJsonOnce();
|
|
25769
|
+
settle(outcome);
|
|
25770
|
+
} else armStallTimer();
|
|
25771
|
+
} catch (error) {
|
|
25772
|
+
fail(/* @__PURE__ */ new Error(`${silence}; status check failed: ${error.message}`));
|
|
25773
|
+
}
|
|
25774
|
+
}, stallTimeoutMs);
|
|
25775
|
+
};
|
|
25486
25776
|
ws.onopen = () => {
|
|
25487
25777
|
ws.send(JSON.stringify({
|
|
25488
25778
|
serverUrl,
|
|
@@ -25490,9 +25780,11 @@ async function streamAgentEventsViaWebSocket(options) {
|
|
|
25490
25780
|
tokenType,
|
|
25491
25781
|
type: "auth"
|
|
25492
25782
|
}));
|
|
25783
|
+
armStallTimer();
|
|
25493
25784
|
};
|
|
25494
25785
|
ws.onmessage = (event) => {
|
|
25495
25786
|
const msg = JSON.parse(event.data);
|
|
25787
|
+
if (msg.type !== "heartbeat_ack") armStallTimer();
|
|
25496
25788
|
if (msg.type === "auth_success") {
|
|
25497
25789
|
log$13.debug("Gateway authenticated");
|
|
25498
25790
|
ws.send(JSON.stringify({
|
|
@@ -25502,11 +25794,11 @@ async function streamAgentEventsViaWebSocket(options) {
|
|
|
25502
25794
|
heartbeatTimer = setInterval(() => {
|
|
25503
25795
|
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: "heartbeat" }));
|
|
25504
25796
|
}, HEARTBEAT_INTERVAL$1);
|
|
25797
|
+
heartbeatTimer.unref?.();
|
|
25505
25798
|
return;
|
|
25506
25799
|
}
|
|
25507
25800
|
if (msg.type === "auth_failed") {
|
|
25508
|
-
|
|
25509
|
-
reject(/* @__PURE__ */ new Error(`Gateway auth failed: ${msg.reason}`));
|
|
25801
|
+
fail(/* @__PURE__ */ new Error(`Gateway auth failed: ${msg.reason}`));
|
|
25510
25802
|
return;
|
|
25511
25803
|
}
|
|
25512
25804
|
if (msg.type === "heartbeat_ack") return;
|
|
@@ -25516,57 +25808,33 @@ async function streamAgentEventsViaWebSocket(options) {
|
|
|
25516
25808
|
if (streamOpts.json) jsonEvents.push(agentEvent);
|
|
25517
25809
|
else renderEvent(agentEvent, ctx, streamOpts);
|
|
25518
25810
|
if (agentEvent.type === "agent_runtime_end") {
|
|
25519
|
-
if (streamOpts.json && !jsonPrinted) {
|
|
25520
|
-
jsonPrinted = true;
|
|
25521
|
-
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25522
|
-
} else if (!streamOpts.json) renderEnd(agentEvent);
|
|
25523
25811
|
if (isSettled) return;
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25812
|
+
const outcome = outcomeFromEndEvent(agentEvent);
|
|
25813
|
+
if (!streamOpts.json) renderEnd(agentEvent, outcome);
|
|
25814
|
+
printJsonOnce();
|
|
25815
|
+
settle(outcome);
|
|
25527
25816
|
return;
|
|
25528
25817
|
}
|
|
25529
25818
|
if (agentEvent.type === "error") {
|
|
25530
|
-
if (
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
}
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
process.exit(1);
|
|
25819
|
+
if (isSettled) return;
|
|
25820
|
+
const outcome = outcomeFromErrorEvent(agentEvent);
|
|
25821
|
+
printJsonOnce();
|
|
25822
|
+
log$13.error(`Agent error: ${outcome.error}`);
|
|
25823
|
+
settle(outcome);
|
|
25824
|
+
return;
|
|
25537
25825
|
}
|
|
25538
25826
|
}
|
|
25539
25827
|
if (msg.type === "session_complete") {
|
|
25540
|
-
|
|
25541
|
-
|
|
25542
|
-
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25543
|
-
}
|
|
25544
|
-
if (isSettled) return;
|
|
25545
|
-
isSettled = true;
|
|
25546
|
-
cleanup();
|
|
25547
|
-
resolve();
|
|
25828
|
+
printJsonOnce();
|
|
25829
|
+
settle(void 0);
|
|
25548
25830
|
}
|
|
25549
25831
|
};
|
|
25550
25832
|
ws.onerror = (err) => {
|
|
25551
|
-
|
|
25552
|
-
if (isSettled) return;
|
|
25553
|
-
if (streamOpts.json && jsonEvents.length > 0 && !jsonPrinted) {
|
|
25554
|
-
jsonPrinted = true;
|
|
25555
|
-
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25556
|
-
}
|
|
25557
|
-
isSettled = true;
|
|
25558
|
-
reject(/* @__PURE__ */ new Error(`Agent gateway WebSocket failed: ${String(err)}`));
|
|
25833
|
+
fail(/* @__PURE__ */ new Error(`Agent gateway WebSocket failed: ${String(err)}`));
|
|
25559
25834
|
};
|
|
25560
25835
|
ws.onclose = (event) => {
|
|
25561
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
25562
|
-
if (isSettled) return;
|
|
25563
|
-
if (streamOpts.json && jsonEvents.length > 0 && !jsonPrinted) {
|
|
25564
|
-
jsonPrinted = true;
|
|
25565
|
-
console.log(JSON.stringify(jsonEvents, null, 2));
|
|
25566
|
-
}
|
|
25567
|
-
isSettled = true;
|
|
25568
25836
|
const reason = event.reason ? `: ${event.reason}` : "";
|
|
25569
|
-
|
|
25837
|
+
fail(/* @__PURE__ */ new Error(`Agent gateway WebSocket closed before completion (code ${event.code}${reason})`));
|
|
25570
25838
|
};
|
|
25571
25839
|
});
|
|
25572
25840
|
}
|
|
@@ -25621,10 +25889,17 @@ function renderEvent(event, ctx, options) {
|
|
|
25621
25889
|
case "step_complete": break;
|
|
25622
25890
|
}
|
|
25623
25891
|
}
|
|
25624
|
-
function
|
|
25892
|
+
function renderOutcome(outcome) {
|
|
25893
|
+
console.log();
|
|
25894
|
+
console.log(describeOutcome(outcome));
|
|
25895
|
+
}
|
|
25896
|
+
function renderEnd(event, outcome) {
|
|
25625
25897
|
console.log();
|
|
25626
|
-
const data =
|
|
25627
|
-
|
|
25898
|
+
const data = {
|
|
25899
|
+
...event.data?.finalState,
|
|
25900
|
+
...event.data
|
|
25901
|
+
};
|
|
25902
|
+
const parts = [describeOutcome(outcome)];
|
|
25628
25903
|
if (data.stepCount !== void 0) parts.push(`${data.stepCount} step${data.stepCount !== 1 ? "s" : ""}`);
|
|
25629
25904
|
if (data.usage?.total_tokens) parts.push(`${data.usage.total_tokens} tokens`);
|
|
25630
25905
|
if (data.cost?.total !== void 0) parts.push(`$${data.cost.total.toFixed(4)}`);
|
|
@@ -25988,6 +26263,133 @@ async function resolveAgentId$1(client, opts) {
|
|
|
25988
26263
|
return "";
|
|
25989
26264
|
}
|
|
25990
26265
|
|
|
26266
|
+
//#endregion
|
|
26267
|
+
//#region src/commands/agent/runStatus.ts
|
|
26268
|
+
const RUN_POLL_INTERVAL_MS = 1e4;
|
|
26269
|
+
/** Upper bound for one status request, so a hung request cannot stall the wait. */
|
|
26270
|
+
const RUN_STATUS_REQUEST_TIMEOUT_MS = 3e4;
|
|
26271
|
+
/** Consecutive failed / unreadable answers tolerated before giving up. */
|
|
26272
|
+
const RUN_STATUS_MAX_MISSES = 3;
|
|
26273
|
+
const sleep$7 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
26274
|
+
const info$2 = (json, msg) => {
|
|
26275
|
+
if (json) console.error(msg);
|
|
26276
|
+
else log$13.info(msg);
|
|
26277
|
+
};
|
|
26278
|
+
const MISSING_RESULT_MESSAGE = "The server returned no status for this operation (unknown id, not visible to this account, or its run state already expired) — the run's outcome cannot be confirmed.";
|
|
26279
|
+
/**
|
|
26280
|
+
* Query `getOperationStatus` with a hard deadline. The abort signal cancels
|
|
26281
|
+
* the request; the race guarantees the deadline even if a transport ignores it.
|
|
26282
|
+
*/
|
|
26283
|
+
const queryOperationStatus = async (client, operationId, timeoutMs = RUN_STATUS_REQUEST_TIMEOUT_MS) => {
|
|
26284
|
+
const controller = new AbortController();
|
|
26285
|
+
let timer;
|
|
26286
|
+
const deadline = new Promise((_, reject) => {
|
|
26287
|
+
timer = setTimeout(() => {
|
|
26288
|
+
controller.abort();
|
|
26289
|
+
reject(/* @__PURE__ */ new Error(`status request got no answer within ${Math.round(timeoutMs / 1e3)}s`));
|
|
26290
|
+
}, timeoutMs);
|
|
26291
|
+
});
|
|
26292
|
+
try {
|
|
26293
|
+
return await Promise.race([client.aiAgent.getOperationStatus.query({ operationId }, { signal: controller.signal }), deadline]);
|
|
26294
|
+
} finally {
|
|
26295
|
+
clearTimeout(timer);
|
|
26296
|
+
}
|
|
26297
|
+
};
|
|
26298
|
+
/**
|
|
26299
|
+
* One status check while the live stream is still open but quiet. Returns the
|
|
26300
|
+
* outcome once the run has ended, `undefined` while it is still active, and
|
|
26301
|
+
* throws when the status cannot be fetched (the caller falls back to polling).
|
|
26302
|
+
*/
|
|
26303
|
+
const probeRunOutcome = async (client, operationId, options = {}) => {
|
|
26304
|
+
const r = await queryOperationStatus(client, operationId, options.requestTimeoutMs);
|
|
26305
|
+
if (!r) {
|
|
26306
|
+
log$13.error(MISSING_RESULT_MESSAGE);
|
|
26307
|
+
return { kind: "unknown" };
|
|
26308
|
+
}
|
|
26309
|
+
const read = readOperationStatus(r);
|
|
26310
|
+
const kind = classifyRunStatus(read.status);
|
|
26311
|
+
if (kind === "active") return void 0;
|
|
26312
|
+
if (!kind) throw new Error(`unreadable run status (${read.status ?? "none"})`);
|
|
26313
|
+
return {
|
|
26314
|
+
error: read.error,
|
|
26315
|
+
kind,
|
|
26316
|
+
status: read.status
|
|
26317
|
+
};
|
|
26318
|
+
};
|
|
26319
|
+
/**
|
|
26320
|
+
* Fallback when the live stream (gateway WebSocket / SSE) is unavailable or
|
|
26321
|
+
* ended without a terminal event: the run may still be executing server-side,
|
|
26322
|
+
* so poll its status until it reaches a terminal state.
|
|
26323
|
+
*
|
|
26324
|
+
* Never reports success it cannot see: failure / interruption statuses come
|
|
26325
|
+
* back as such, and a missing, unreadable or unreachable status ends as
|
|
26326
|
+
* `unknown` instead of being read as "finished".
|
|
26327
|
+
*/
|
|
26328
|
+
const pollAgentRunStatus = async (client, operationId, options = {}) => {
|
|
26329
|
+
const { intervalMs = RUN_POLL_INTERVAL_MS, json, maxMisses = 3, requestTimeoutMs } = options;
|
|
26330
|
+
const finish = (outcome) => {
|
|
26331
|
+
if (!json) {
|
|
26332
|
+
console.log();
|
|
26333
|
+
console.log(describeOutcome(outcome));
|
|
26334
|
+
}
|
|
26335
|
+
return outcome;
|
|
26336
|
+
};
|
|
26337
|
+
let lastStatus = "";
|
|
26338
|
+
let failures = 0;
|
|
26339
|
+
let unreadable = 0;
|
|
26340
|
+
for (let i = 0;; i++) {
|
|
26341
|
+
if (i > 0) await sleep$7(intervalMs);
|
|
26342
|
+
let r;
|
|
26343
|
+
try {
|
|
26344
|
+
r = await queryOperationStatus(client, operationId, requestTimeoutMs);
|
|
26345
|
+
failures = 0;
|
|
26346
|
+
} catch (error) {
|
|
26347
|
+
failures++;
|
|
26348
|
+
log$13.warn(`Status poll failed (${failures}/${maxMisses}): ${error.message}`);
|
|
26349
|
+
if (failures >= maxMisses) {
|
|
26350
|
+
log$13.error("Giving up on the run status; the run may still be executing server-side.");
|
|
26351
|
+
return finish({
|
|
26352
|
+
error: error.message,
|
|
26353
|
+
kind: "unknown"
|
|
26354
|
+
});
|
|
26355
|
+
}
|
|
26356
|
+
continue;
|
|
26357
|
+
}
|
|
26358
|
+
if (!r) {
|
|
26359
|
+
log$13.error(MISSING_RESULT_MESSAGE);
|
|
26360
|
+
return finish({ kind: "unknown" });
|
|
26361
|
+
}
|
|
26362
|
+
const read = readOperationStatus(r);
|
|
26363
|
+
const kind = classifyRunStatus(read.status);
|
|
26364
|
+
if (!kind) {
|
|
26365
|
+
unreadable++;
|
|
26366
|
+
if (unreadable >= maxMisses) {
|
|
26367
|
+
const keys = typeof r === "object" ? Object.keys(r).join(", ") : typeof r;
|
|
26368
|
+
log$13.error(`Could not read a run status from the server's answer (status: ${read.status ?? "none"}; keys: ${keys}).`);
|
|
26369
|
+
return finish({
|
|
26370
|
+
kind: "unknown",
|
|
26371
|
+
status: read.status
|
|
26372
|
+
});
|
|
26373
|
+
}
|
|
26374
|
+
continue;
|
|
26375
|
+
}
|
|
26376
|
+
unreadable = 0;
|
|
26377
|
+
if (read.status !== lastStatus) {
|
|
26378
|
+
lastStatus = read.status;
|
|
26379
|
+
const steps = read.stepCount !== void 0 ? ` · ${read.stepCount} step(s)` : "";
|
|
26380
|
+
info$2(json, `Run status: ${colorStatus$1(read.status)}${steps}`);
|
|
26381
|
+
}
|
|
26382
|
+
if (kind !== "active") {
|
|
26383
|
+
if (read.error) log$13.error(`Run error: ${read.error}`);
|
|
26384
|
+
return finish({
|
|
26385
|
+
error: read.error,
|
|
26386
|
+
kind,
|
|
26387
|
+
status: read.status
|
|
26388
|
+
});
|
|
26389
|
+
}
|
|
26390
|
+
}
|
|
26391
|
+
};
|
|
26392
|
+
|
|
25991
26393
|
//#endregion
|
|
25992
26394
|
//#region node_modules/.pnpm/dayjs@1.11.23/node_modules/dayjs/dayjs.min.js
|
|
25993
26395
|
var require_dayjs_min = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -26428,7 +26830,7 @@ function formatFsLongListing(nodes) {
|
|
|
26428
26830
|
return lines;
|
|
26429
26831
|
}
|
|
26430
26832
|
async function readFsContentInput(options) {
|
|
26431
|
-
if (options.contentFile) return readFileSync
|
|
26833
|
+
if (options.contentFile) return readFileSync(options.contentFile, "utf8");
|
|
26432
26834
|
if (options.content !== void 0) return options.content;
|
|
26433
26835
|
if (process.stdin.isTTY === false) return text(process.stdin);
|
|
26434
26836
|
return "";
|
|
@@ -26989,14 +27391,20 @@ function registerAgentCommand(program) {
|
|
|
26989
27391
|
const r = await client.agent.duplicateAgent.mutate(input);
|
|
26990
27392
|
console.log(`${import_picocolors.default.green("✓")} Duplicated agent → ${import_picocolors.default.bold(r.agentId || r.id || "done")}`);
|
|
26991
27393
|
});
|
|
26992
|
-
agent.command("run").description("Run an agent with a prompt").option("-a, --agent-id <id>", "Agent ID").option("-s, --slug <slug>", "Agent slug").option("-p, --prompt <text>", "User prompt").option("-t, --topic-id <id>", "Reuse an existing topic").option("--no-auto-start", "Do not auto-start the agent").option("--device <target>", "Target device ID, or use \"local\" for the current connected device").option("--no-headless", "Disable headless mode
|
|
27394
|
+
agent.command("run").description("Run an agent with a prompt").option("-a, --agent-id <id>", "Agent ID").option("-s, --slug <slug>", "Agent slug").option("-p, --prompt <text>", "User prompt").option("-t, --topic-id <id>", "Reuse an existing topic").option("--no-auto-start", "Do not auto-start the agent").option("--device <target>", "Target device ID, or use \"local\" for the current connected device").option("--no-headless", "Disable headless mode: tool calls that need approval park the run for a human (default: headless — tools auto-run, matching the CLI's non-interactive nature). The CLI does not wait for that approval; it returns with exit code 2 and the run continues once it is answered in LobeHub").option("--json", "Output full JSON event stream").option("-v, --verbose", "Show detailed tool call info").option("--replay <file>", "Replay events from a saved JSON file (offline)").option("--sse", "Force SSE stream instead of WebSocket gateway").option("--timeout <seconds>", "Stop waiting after this many seconds (exit code 3; the run keeps going server-side). Default: wait as long as the server reports the run active", parsePositiveSeconds).addHelpText("after", `
|
|
27395
|
+
Exit codes:
|
|
27396
|
+
0 the run finished (done)
|
|
27397
|
+
1 the run failed or was interrupted
|
|
27398
|
+
2 the run is paused waiting for human approval / input
|
|
27399
|
+
3 the outcome could not be confirmed (no or unreadable status, status checks kept failing, or --timeout reached)`).action(async (options) => {
|
|
26993
27400
|
if (options.verbose) setVerbose(true);
|
|
26994
27401
|
if (options.replay) {
|
|
26995
27402
|
const data = readFileSync(options.replay, "utf8");
|
|
26996
|
-
replayAgentEvents(JSON.parse(data), {
|
|
27403
|
+
const replayExitCode = AGENT_RUN_EXIT_CODES[(replayAgentEvents(JSON.parse(data), {
|
|
26997
27404
|
json: options.json,
|
|
26998
27405
|
verbose: options.verbose
|
|
26999
|
-
});
|
|
27406
|
+
}) ?? { kind: "unknown" }).kind];
|
|
27407
|
+
if (replayExitCode !== 0) process.exitCode = replayExitCode;
|
|
27000
27408
|
return;
|
|
27001
27409
|
}
|
|
27002
27410
|
if (!options.agentId && !options.slug) {
|
|
@@ -27051,24 +27459,45 @@ function registerAgentCommand(program) {
|
|
|
27051
27459
|
if (!options.json) log$13.info(`Operation: ${import_picocolors.default.dim(operationId)} · Topic: ${import_picocolors.default.dim(r.topicId || "n/a")}`);
|
|
27052
27460
|
const { serverUrl, headers, token, tokenType } = await getAgentStreamAuthInfo();
|
|
27053
27461
|
const agentGatewayUrl = options.sse ? void 0 : resolveAgentGatewayUrl();
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27057
|
-
|
|
27058
|
-
|
|
27059
|
-
|
|
27060
|
-
|
|
27061
|
-
|
|
27062
|
-
|
|
27063
|
-
|
|
27064
|
-
|
|
27065
|
-
|
|
27066
|
-
|
|
27067
|
-
|
|
27068
|
-
|
|
27069
|
-
|
|
27070
|
-
|
|
27071
|
-
|
|
27462
|
+
const waitForRun = async () => {
|
|
27463
|
+
let streamed;
|
|
27464
|
+
try {
|
|
27465
|
+
if (agentGatewayUrl) streamed = await streamAgentEventsViaWebSocket({
|
|
27466
|
+
gatewayUrl: agentGatewayUrl,
|
|
27467
|
+
json: options.json,
|
|
27468
|
+
onStall: () => probeRunOutcome(client, operationId, { json: options.json }),
|
|
27469
|
+
operationId,
|
|
27470
|
+
serverUrl,
|
|
27471
|
+
token,
|
|
27472
|
+
tokenType,
|
|
27473
|
+
verbose: options.verbose
|
|
27474
|
+
});
|
|
27475
|
+
else streamed = await streamAgentEvents(`${serverUrl}/api/agent/stream?operationId=${encodeURIComponent(operationId)}`, headers, {
|
|
27476
|
+
json: options.json,
|
|
27477
|
+
onStall: () => probeRunOutcome(client, operationId, { json: options.json }),
|
|
27478
|
+
verbose: options.verbose
|
|
27479
|
+
});
|
|
27480
|
+
} catch (error) {
|
|
27481
|
+
log$13.warn(`Live stream unavailable (${error.message}). Polling run status every ${RUN_POLL_INTERVAL_MS / 1e3}s…`);
|
|
27482
|
+
return pollAgentRunStatus(client, operationId, { json: options.json });
|
|
27483
|
+
}
|
|
27484
|
+
return streamed ?? pollAgentRunStatus(client, operationId, { json: options.json });
|
|
27485
|
+
};
|
|
27486
|
+
let timeoutTimer;
|
|
27487
|
+
let didTimeOut = false;
|
|
27488
|
+
const timedOut = options.timeout === void 0 ? void 0 : new Promise((resolve) => {
|
|
27489
|
+
timeoutTimer = setTimeout(() => {
|
|
27490
|
+
didTimeOut = true;
|
|
27491
|
+
log$13.error(`Stopped waiting after ${options.timeout}s (--timeout). The run may still be executing — check it with 'lh agent status ${operationId}'.`);
|
|
27492
|
+
resolve({ kind: "unknown" });
|
|
27493
|
+
}, options.timeout * 1e3);
|
|
27494
|
+
});
|
|
27495
|
+
const outcome = await (timedOut ? Promise.race([waitForRun(), timedOut]) : waitForRun());
|
|
27496
|
+
clearTimeout(timeoutTimer);
|
|
27497
|
+
const exitCode = AGENT_RUN_EXIT_CODES[outcome.kind];
|
|
27498
|
+
if (exitCode !== 0) {
|
|
27499
|
+
if (didTimeOut) process.exit(exitCode);
|
|
27500
|
+
process.exitCode = exitCode;
|
|
27072
27501
|
}
|
|
27073
27502
|
});
|
|
27074
27503
|
agent.command("pin <agentId>").description("Pin an agent").action(async (agentId) => {
|
|
@@ -27204,13 +27633,19 @@ function registerAgentCommand(program) {
|
|
|
27204
27633
|
outputJson(r, typeof options.json === "string" ? options.json : void 0);
|
|
27205
27634
|
return;
|
|
27206
27635
|
}
|
|
27636
|
+
if (!r) {
|
|
27637
|
+
log$13.error(`No status for operation ${operationId} (unknown id, not visible to this account, or its run state already expired).`);
|
|
27638
|
+
process.exit(1);
|
|
27639
|
+
return;
|
|
27640
|
+
}
|
|
27641
|
+
const read = readOperationStatus(r);
|
|
27207
27642
|
console.log(import_picocolors.default.bold("Operation Status"));
|
|
27208
27643
|
console.log(` ID: ${operationId}`);
|
|
27209
|
-
console.log(` Status: ${colorStatus$1(
|
|
27210
|
-
if (
|
|
27211
|
-
if (
|
|
27212
|
-
if (
|
|
27213
|
-
if (
|
|
27644
|
+
console.log(` Status: ${colorStatus$1(read.status || "unknown")}`);
|
|
27645
|
+
if (read.stepCount !== void 0) console.log(` Steps: ${read.stepCount}`);
|
|
27646
|
+
if (read.usage?.total_tokens) console.log(` Tokens: ${read.usage.total_tokens}`);
|
|
27647
|
+
if (read.cost?.total !== void 0) console.log(` Cost: $${read.cost.total.toFixed(4)}`);
|
|
27648
|
+
if (read.error) console.log(` Error: ${import_picocolors.default.red(read.error)}`);
|
|
27214
27649
|
if (r.createdAt) console.log(` Started: ${r.createdAt}`);
|
|
27215
27650
|
if (r.completedAt) console.log(` Ended: ${r.completedAt}`);
|
|
27216
27651
|
});
|
|
@@ -27232,60 +27667,10 @@ function registerAgentCommand(program) {
|
|
|
27232
27667
|
else console.log(`${import_picocolors.default.yellow("!")} Interrupt not acknowledged for ${import_picocolors.default.bold(label)} (already finished?)`);
|
|
27233
27668
|
});
|
|
27234
27669
|
}
|
|
27235
|
-
function
|
|
27236
|
-
|
|
27237
|
-
|
|
27238
|
-
|
|
27239
|
-
case "failed":
|
|
27240
|
-
case "error": return import_picocolors.default.red(status);
|
|
27241
|
-
case "processing":
|
|
27242
|
-
case "running": return import_picocolors.default.yellow(status);
|
|
27243
|
-
default: return import_picocolors.default.dim(status);
|
|
27244
|
-
}
|
|
27245
|
-
}
|
|
27246
|
-
const TERMINAL_RUN_STATUSES = new Set([
|
|
27247
|
-
"completed",
|
|
27248
|
-
"done",
|
|
27249
|
-
"success",
|
|
27250
|
-
"failed",
|
|
27251
|
-
"error",
|
|
27252
|
-
"cancelled",
|
|
27253
|
-
"canceled",
|
|
27254
|
-
"aborted"
|
|
27255
|
-
]);
|
|
27256
|
-
/**
|
|
27257
|
-
* Fallback when the live stream (gateway WebSocket / SSE) drops before the run
|
|
27258
|
-
* finishes: the run is still executing server-side, so poll its status every 10s
|
|
27259
|
-
* until it reaches a terminal state (or is no longer tracked, which also means it
|
|
27260
|
-
* has finished). Avoids hard-exiting on a transient gateway disconnect.
|
|
27261
|
-
*/
|
|
27262
|
-
async function pollAgentRunStatus(client, operationId) {
|
|
27263
|
-
const POLL_MS = 1e4;
|
|
27264
|
-
let lastStatus = "";
|
|
27265
|
-
for (let i = 0;; i++) {
|
|
27266
|
-
if (i > 0) await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
27267
|
-
let r;
|
|
27268
|
-
try {
|
|
27269
|
-
r = await client.aiAgent.getOperationStatus.query({ operationId });
|
|
27270
|
-
} catch (error) {
|
|
27271
|
-
log$13.error(`Status poll failed: ${error.message}`);
|
|
27272
|
-
process.exit(1);
|
|
27273
|
-
}
|
|
27274
|
-
if (!r) {
|
|
27275
|
-
log$13.info("Run is no longer tracked — finished (or expired).");
|
|
27276
|
-
return;
|
|
27277
|
-
}
|
|
27278
|
-
const status = r.status || r.state || "unknown";
|
|
27279
|
-
if (status !== lastStatus) {
|
|
27280
|
-
lastStatus = status;
|
|
27281
|
-
const steps = r.stepCount !== void 0 ? ` · ${r.stepCount} step(s)` : "";
|
|
27282
|
-
log$13.info(`Run status: ${colorStatus$1(status)}${steps}`);
|
|
27283
|
-
}
|
|
27284
|
-
if (TERMINAL_RUN_STATUSES.has(status)) {
|
|
27285
|
-
if (r.error) log$13.error(`Run error: ${r.error}`);
|
|
27286
|
-
return;
|
|
27287
|
-
}
|
|
27288
|
-
}
|
|
27670
|
+
function parsePositiveSeconds(value) {
|
|
27671
|
+
const seconds = Number(value);
|
|
27672
|
+
if (!Number.isFinite(seconds) || seconds <= 0) throw new InvalidArgumentError("Expected a positive number of seconds.");
|
|
27673
|
+
return seconds;
|
|
27289
27674
|
}
|
|
27290
27675
|
|
|
27291
27676
|
//#endregion
|
|
@@ -38457,6 +38842,7 @@ const isObjectLike = (value) => typeof value === "object" && value !== null;
|
|
|
38457
38842
|
*/
|
|
38458
38843
|
const isRecord$6 = (value) => isObjectLike(value) && !Array.isArray(value);
|
|
38459
38844
|
const toRecord = (value) => isRecord$6(value) ? value : void 0;
|
|
38845
|
+
const pickString$1 = (value) => typeof value === "string" ? value : void 0;
|
|
38460
38846
|
|
|
38461
38847
|
//#endregion
|
|
38462
38848
|
//#region node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/options.js
|
|
@@ -41667,6 +42053,503 @@ var init_cliSpawn = __esmMin((() => {
|
|
|
41667
42053
|
};
|
|
41668
42054
|
}));
|
|
41669
42055
|
|
|
42056
|
+
//#endregion
|
|
42057
|
+
//#region ../../packages/heterogeneous-agents/src/codex/CodexAppServerClient.ts
|
|
42058
|
+
init_cliSpawn();
|
|
42059
|
+
const APP_SERVER_RPC_TIMEOUT_MS = 3e4;
|
|
42060
|
+
const DEFAULT_RECONNECT_BASE_DELAY_MS = 250;
|
|
42061
|
+
const DEFAULT_RECONNECT_MAX_DELAY_MS = 4e3;
|
|
42062
|
+
const DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;
|
|
42063
|
+
const APPROVAL_REQUEST_METHODS = new Set(["item/commandExecution/requestApproval", "item/fileChange/requestApproval"]);
|
|
42064
|
+
var CodexAppServerRpcError = class extends Error {
|
|
42065
|
+
constructor(message, code, data, method) {
|
|
42066
|
+
super(message);
|
|
42067
|
+
this.code = code;
|
|
42068
|
+
this.data = data;
|
|
42069
|
+
this.method = method;
|
|
42070
|
+
this.name = "CodexAppServerRpcError";
|
|
42071
|
+
}
|
|
42072
|
+
};
|
|
42073
|
+
var CodexAppServerConnectionError = class extends Error {
|
|
42074
|
+
phase;
|
|
42075
|
+
constructor(message, options) {
|
|
42076
|
+
super(message, options);
|
|
42077
|
+
this.name = "CodexAppServerConnectionError";
|
|
42078
|
+
this.phase = options?.phase;
|
|
42079
|
+
}
|
|
42080
|
+
};
|
|
42081
|
+
/**
|
|
42082
|
+
* One long-lived, bidirectional NDJSON client for `codex app-server`.
|
|
42083
|
+
*
|
|
42084
|
+
* stdout has exactly one reader here. It correlates RPC responses, publishes
|
|
42085
|
+
* notifications to the owning thread session, and separately routes
|
|
42086
|
+
* server-initiated requests back to that thread.
|
|
42087
|
+
*/
|
|
42088
|
+
var CodexAppServerClient = class {
|
|
42089
|
+
activeGeneration = 0;
|
|
42090
|
+
closedByHost = false;
|
|
42091
|
+
connectionState;
|
|
42092
|
+
connectionError;
|
|
42093
|
+
connected = false;
|
|
42094
|
+
consumerCount = 0;
|
|
42095
|
+
generationSequence = 0;
|
|
42096
|
+
hasConnected = false;
|
|
42097
|
+
nextRequestId = 0;
|
|
42098
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
42099
|
+
notificationHandlers = /* @__PURE__ */ new Map();
|
|
42100
|
+
serverRequestHandlers = /* @__PURE__ */ new Map();
|
|
42101
|
+
disconnectHandlers = /* @__PURE__ */ new Set();
|
|
42102
|
+
rawMessageHandlers = /* @__PURE__ */ new Map();
|
|
42103
|
+
stderrHandlers = /* @__PURE__ */ new Set();
|
|
42104
|
+
threadRegistrations = /* @__PURE__ */ new Map();
|
|
42105
|
+
processGeneration;
|
|
42106
|
+
reconnectAttempt = 0;
|
|
42107
|
+
reconnectEpoch = 0;
|
|
42108
|
+
reconnectExhaustedEpoch;
|
|
42109
|
+
reconnectTimer;
|
|
42110
|
+
constructor(options) {
|
|
42111
|
+
this.options = options;
|
|
42112
|
+
}
|
|
42113
|
+
get isConnected() {
|
|
42114
|
+
return this.connected && !this.connectionError;
|
|
42115
|
+
}
|
|
42116
|
+
get hasConsumers() {
|
|
42117
|
+
return this.consumerCount > 0;
|
|
42118
|
+
}
|
|
42119
|
+
acquireConsumer() {
|
|
42120
|
+
if (this.closedByHost) throw new CodexAppServerConnectionError("Codex app-server client closed by host");
|
|
42121
|
+
this.consumerCount += 1;
|
|
42122
|
+
let released = false;
|
|
42123
|
+
return () => {
|
|
42124
|
+
if (released) return;
|
|
42125
|
+
released = true;
|
|
42126
|
+
this.consumerCount -= 1;
|
|
42127
|
+
};
|
|
42128
|
+
}
|
|
42129
|
+
/** Process-global options must stay identical while this long-lived client is reused. */
|
|
42130
|
+
canReuseFor(options) {
|
|
42131
|
+
const currentArgs = this.options.args ?? [];
|
|
42132
|
+
const nextArgs = options.args ?? [];
|
|
42133
|
+
const commandIsRelativePath = !path.isAbsolute(this.options.commandPath) && (this.options.commandPath.includes("/") || this.options.commandPath.includes("\\"));
|
|
42134
|
+
if (this.options.commandPath !== options.commandPath || commandIsRelativePath && this.options.cwd !== options.cwd || currentArgs.length !== nextArgs.length || currentArgs.some((arg, index) => arg !== nextArgs[index])) return false;
|
|
42135
|
+
const currentEnv = Object.entries(this.options.env).filter(([, value]) => value !== void 0);
|
|
42136
|
+
const nextEnv = Object.entries(options.env).filter(([, value]) => value !== void 0);
|
|
42137
|
+
return currentEnv.length === nextEnv.length && currentEnv.every(([key, value]) => options.env[key] === value);
|
|
42138
|
+
}
|
|
42139
|
+
connect() {
|
|
42140
|
+
if (this.closedByHost) return Promise.reject(new CodexAppServerConnectionError("Codex app-server client closed by host"));
|
|
42141
|
+
if (this.connectionState && !this.connectionError) return this.connectionState.promise;
|
|
42142
|
+
this.startUserRecoveryEpoch();
|
|
42143
|
+
return this.startConnection();
|
|
42144
|
+
}
|
|
42145
|
+
async request(method, params) {
|
|
42146
|
+
if (!this.connected) await this.connect();
|
|
42147
|
+
if (this.connectionError) throw this.connectionError;
|
|
42148
|
+
return this.requestForGeneration(this.activeGeneration, method, params);
|
|
42149
|
+
}
|
|
42150
|
+
notify(notification) {
|
|
42151
|
+
this.writeForGeneration(this.activeGeneration, { ...notification });
|
|
42152
|
+
}
|
|
42153
|
+
subscribe(threadId, handler) {
|
|
42154
|
+
const handlers = this.notificationHandlers.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
42155
|
+
handlers.add(handler);
|
|
42156
|
+
this.notificationHandlers.set(threadId, handlers);
|
|
42157
|
+
return () => this.removeHandler(this.notificationHandlers, threadId, handler);
|
|
42158
|
+
}
|
|
42159
|
+
subscribeServerRequests(threadId, handler) {
|
|
42160
|
+
const handlers = this.serverRequestHandlers.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
42161
|
+
handlers.add(handler);
|
|
42162
|
+
this.serverRequestHandlers.set(threadId, handlers);
|
|
42163
|
+
return () => this.removeHandler(this.serverRequestHandlers, threadId, handler);
|
|
42164
|
+
}
|
|
42165
|
+
registerThread(threadId, params, registration) {
|
|
42166
|
+
if (params.threadId !== threadId) throw new Error(`Codex resume params do not match registered thread: ${threadId}`);
|
|
42167
|
+
const existing = this.threadRegistrations.get(threadId);
|
|
42168
|
+
if (existing && JSON.stringify(existing.params) !== JSON.stringify(params)) throw new Error(`Conflicting Codex resume params for thread: ${threadId}`);
|
|
42169
|
+
const group = existing ?? {
|
|
42170
|
+
params,
|
|
42171
|
+
registrations: /* @__PURE__ */ new Set()
|
|
42172
|
+
};
|
|
42173
|
+
group.registrations.add(registration);
|
|
42174
|
+
this.threadRegistrations.set(threadId, group);
|
|
42175
|
+
if (!this.connected && this.connectionError) this.scheduleReconnect();
|
|
42176
|
+
return () => {
|
|
42177
|
+
group.registrations.delete(registration);
|
|
42178
|
+
if (group.registrations.size === 0) this.threadRegistrations.delete(threadId);
|
|
42179
|
+
if (this.threadRegistrations.size === 0 && this.reconnectTimer) {
|
|
42180
|
+
clearTimeout(this.reconnectTimer);
|
|
42181
|
+
this.reconnectTimer = void 0;
|
|
42182
|
+
}
|
|
42183
|
+
};
|
|
42184
|
+
}
|
|
42185
|
+
onDisconnect(handler) {
|
|
42186
|
+
this.disconnectHandlers.add(handler);
|
|
42187
|
+
return () => this.disconnectHandlers.delete(handler);
|
|
42188
|
+
}
|
|
42189
|
+
onRawMessage(threadId, handler) {
|
|
42190
|
+
const handlers = this.rawMessageHandlers.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
42191
|
+
handlers.add(handler);
|
|
42192
|
+
this.rawMessageHandlers.set(threadId, handlers);
|
|
42193
|
+
return () => this.removeHandler(this.rawMessageHandlers, threadId, handler);
|
|
42194
|
+
}
|
|
42195
|
+
onStderr(handler) {
|
|
42196
|
+
this.stderrHandlers.add(handler);
|
|
42197
|
+
return () => this.stderrHandlers.delete(handler);
|
|
42198
|
+
}
|
|
42199
|
+
close() {
|
|
42200
|
+
if (this.closedByHost) return;
|
|
42201
|
+
this.closedByHost = true;
|
|
42202
|
+
this.reconnectEpoch += 1;
|
|
42203
|
+
if (this.reconnectTimer) {
|
|
42204
|
+
clearTimeout(this.reconnectTimer);
|
|
42205
|
+
this.reconnectTimer = void 0;
|
|
42206
|
+
}
|
|
42207
|
+
const error = new CodexAppServerConnectionError("Codex app-server client closed by host");
|
|
42208
|
+
const generation = this.activeGeneration;
|
|
42209
|
+
this.activeGeneration = ++this.generationSequence;
|
|
42210
|
+
this.connected = false;
|
|
42211
|
+
this.connectionError = error;
|
|
42212
|
+
this.connectionState = void 0;
|
|
42213
|
+
const processGeneration = this.processGeneration;
|
|
42214
|
+
this.processGeneration = void 0;
|
|
42215
|
+
this.rejectPendingRequests(generation, error);
|
|
42216
|
+
this.emitDisconnect(error);
|
|
42217
|
+
this.terminateChild(processGeneration?.child);
|
|
42218
|
+
}
|
|
42219
|
+
startConnection() {
|
|
42220
|
+
if (this.closedByHost) return Promise.reject(new CodexAppServerConnectionError("Codex app-server client closed by host"));
|
|
42221
|
+
const generation = ++this.generationSequence;
|
|
42222
|
+
const isReconnect = this.hasConnected;
|
|
42223
|
+
this.activeGeneration = generation;
|
|
42224
|
+
this.connected = false;
|
|
42225
|
+
this.connectionError = void 0;
|
|
42226
|
+
const promise = this.initialize(generation, isReconnect);
|
|
42227
|
+
this.connectionState = {
|
|
42228
|
+
generation,
|
|
42229
|
+
promise
|
|
42230
|
+
};
|
|
42231
|
+
return promise;
|
|
42232
|
+
}
|
|
42233
|
+
async initialize(generation, isReconnect) {
|
|
42234
|
+
try {
|
|
42235
|
+
await this.startProcess(generation);
|
|
42236
|
+
const params = {
|
|
42237
|
+
capabilities: {
|
|
42238
|
+
experimentalApi: false,
|
|
42239
|
+
requestAttestation: false
|
|
42240
|
+
},
|
|
42241
|
+
clientInfo: {
|
|
42242
|
+
name: "lobehub-desktop",
|
|
42243
|
+
title: "LobeHub Desktop",
|
|
42244
|
+
version: this.options.clientVersion
|
|
42245
|
+
}
|
|
42246
|
+
};
|
|
42247
|
+
const response = await this.requestForGeneration(generation, "initialize", params);
|
|
42248
|
+
this.writeForGeneration(generation, { method: "initialized" });
|
|
42249
|
+
if (!this.isCurrentGeneration(generation)) throw new CodexAppServerConnectionError("Codex app-server connection was replaced");
|
|
42250
|
+
this.connected = true;
|
|
42251
|
+
this.hasConnected = true;
|
|
42252
|
+
if (isReconnect) {
|
|
42253
|
+
await this.resumeRegisteredThreads(generation);
|
|
42254
|
+
if (!this.isCurrentGeneration(generation) || !this.connected || this.connectionError) throw this.connectionError ?? new CodexAppServerConnectionError("Codex app-server disconnected while resuming threads");
|
|
42255
|
+
}
|
|
42256
|
+
this.reconnectAttempt = 0;
|
|
42257
|
+
this.reconnectExhaustedEpoch = void 0;
|
|
42258
|
+
return response;
|
|
42259
|
+
} catch (error) {
|
|
42260
|
+
const failure = this.toConnectionError(error, "Codex app-server handshake failed");
|
|
42261
|
+
if (!this.isCurrentGeneration(generation)) throw failure;
|
|
42262
|
+
const connectionError = !isReconnect && failure instanceof CodexAppServerConnectionError ? new CodexAppServerConnectionError(failure.message, {
|
|
42263
|
+
cause: failure,
|
|
42264
|
+
phase: "initialize"
|
|
42265
|
+
}) : failure;
|
|
42266
|
+
this.fail(connectionError, generation);
|
|
42267
|
+
throw connectionError;
|
|
42268
|
+
}
|
|
42269
|
+
}
|
|
42270
|
+
async startProcess(generation) {
|
|
42271
|
+
const spawnPlan = await resolveCliSpawnPlan(this.options.commandPath, [...this.options.args ?? [], "app-server"]);
|
|
42272
|
+
if (!this.isCurrentGeneration(generation)) throw new CodexAppServerConnectionError(this.closedByHost ? "Codex app-server client closed by host" : "Codex app-server connection was replaced");
|
|
42273
|
+
const child = spawnManaged(spawnPlan.command, spawnPlan.args, {
|
|
42274
|
+
cwd: this.options.cwd,
|
|
42275
|
+
detached: process.platform !== "win32",
|
|
42276
|
+
env: this.options.env,
|
|
42277
|
+
stdio: [
|
|
42278
|
+
"pipe",
|
|
42279
|
+
"pipe",
|
|
42280
|
+
"pipe"
|
|
42281
|
+
]
|
|
42282
|
+
});
|
|
42283
|
+
const processGeneration = {
|
|
42284
|
+
child,
|
|
42285
|
+
generation,
|
|
42286
|
+
stdoutBuffer: ""
|
|
42287
|
+
};
|
|
42288
|
+
if (!this.isCurrentGeneration(generation)) {
|
|
42289
|
+
this.terminateChild(child);
|
|
42290
|
+
throw new CodexAppServerConnectionError("Codex app-server connection was replaced");
|
|
42291
|
+
}
|
|
42292
|
+
this.processGeneration = processGeneration;
|
|
42293
|
+
child.stdin?.on("error", () => {});
|
|
42294
|
+
child.stdout?.on("data", (chunk) => {
|
|
42295
|
+
if (!this.ownsProcess(processGeneration)) return;
|
|
42296
|
+
this.consumeStdout(processGeneration, chunk);
|
|
42297
|
+
});
|
|
42298
|
+
child.stderr?.on("data", (chunk) => {
|
|
42299
|
+
if (!this.ownsProcess(processGeneration)) return;
|
|
42300
|
+
this.emitText(this.stderrHandlers, chunk.toString());
|
|
42301
|
+
});
|
|
42302
|
+
child.once("error", (error) => {
|
|
42303
|
+
if (!this.ownsProcess(processGeneration)) return;
|
|
42304
|
+
this.fail(new CodexAppServerConnectionError(`Failed to start Codex app-server: ${error.message}`, { cause: error }), generation);
|
|
42305
|
+
});
|
|
42306
|
+
child.once("exit", (code, signal) => {
|
|
42307
|
+
if (!this.ownsProcess(processGeneration)) return;
|
|
42308
|
+
this.fail(new CodexAppServerConnectionError(`Codex app-server exited (code ${code ?? "null"}, signal ${signal ?? "null"})`), generation);
|
|
42309
|
+
});
|
|
42310
|
+
}
|
|
42311
|
+
requestForGeneration(generation, method, params) {
|
|
42312
|
+
const processGeneration = this.processGeneration;
|
|
42313
|
+
if (!processGeneration || !this.ownsProcess(processGeneration) || generation !== this.activeGeneration) return Promise.reject(new CodexAppServerConnectionError("Codex app-server stdin is unavailable"));
|
|
42314
|
+
const id = ++this.nextRequestId;
|
|
42315
|
+
const key = String(id);
|
|
42316
|
+
return new Promise((resolve, reject) => {
|
|
42317
|
+
const timeout = setTimeout(() => {
|
|
42318
|
+
const current = this.pendingRequests.get(key);
|
|
42319
|
+
if (!current || current.generation !== generation) return;
|
|
42320
|
+
this.pendingRequests.delete(key);
|
|
42321
|
+
const error = new CodexAppServerConnectionError(`Codex app-server request timed out: ${method}`);
|
|
42322
|
+
reject(error);
|
|
42323
|
+
this.fail(error, generation);
|
|
42324
|
+
}, APP_SERVER_RPC_TIMEOUT_MS);
|
|
42325
|
+
timeout.unref?.();
|
|
42326
|
+
const pending = {
|
|
42327
|
+
generation,
|
|
42328
|
+
method,
|
|
42329
|
+
reject,
|
|
42330
|
+
resolve: (result) => resolve(result),
|
|
42331
|
+
threadId: isRecord$6(params) ? pickString$1(params.threadId) : void 0,
|
|
42332
|
+
timeout
|
|
42333
|
+
};
|
|
42334
|
+
this.pendingRequests.set(key, pending);
|
|
42335
|
+
try {
|
|
42336
|
+
this.writeToProcess(processGeneration, {
|
|
42337
|
+
id,
|
|
42338
|
+
method,
|
|
42339
|
+
params
|
|
42340
|
+
});
|
|
42341
|
+
} catch (error) {
|
|
42342
|
+
if (this.pendingRequests.get(key) === pending) this.pendingRequests.delete(key);
|
|
42343
|
+
clearTimeout(timeout);
|
|
42344
|
+
const connectionError = this.toConnectionError(error, `Failed to write Codex app-server request: ${method}`);
|
|
42345
|
+
reject(connectionError);
|
|
42346
|
+
this.fail(connectionError, generation);
|
|
42347
|
+
}
|
|
42348
|
+
});
|
|
42349
|
+
}
|
|
42350
|
+
consumeStdout(processGeneration, chunk) {
|
|
42351
|
+
processGeneration.stdoutBuffer += chunk.toString("utf8");
|
|
42352
|
+
let newlineIndex;
|
|
42353
|
+
while ((newlineIndex = processGeneration.stdoutBuffer.indexOf("\n")) >= 0) {
|
|
42354
|
+
const line = processGeneration.stdoutBuffer.slice(0, newlineIndex).trim();
|
|
42355
|
+
processGeneration.stdoutBuffer = processGeneration.stdoutBuffer.slice(newlineIndex + 1);
|
|
42356
|
+
if (!line) continue;
|
|
42357
|
+
try {
|
|
42358
|
+
const message = JSON.parse(line);
|
|
42359
|
+
const threadId = this.getMessageThreadId(processGeneration.generation, message);
|
|
42360
|
+
const rawHandlers = threadId ? this.rawMessageHandlers.get(threadId) : void 0;
|
|
42361
|
+
if (rawHandlers) this.emitText(rawHandlers, `${line}\n`);
|
|
42362
|
+
this.routeMessage(processGeneration.generation, message);
|
|
42363
|
+
} catch (error) {
|
|
42364
|
+
this.fail(new CodexAppServerConnectionError("Codex app-server emitted invalid NDJSON", { cause: error }), processGeneration.generation);
|
|
42365
|
+
return;
|
|
42366
|
+
}
|
|
42367
|
+
}
|
|
42368
|
+
}
|
|
42369
|
+
getMessageThreadId(generation, message) {
|
|
42370
|
+
if (!isRecord$6(message)) return;
|
|
42371
|
+
if (isRecord$6(message.params)) {
|
|
42372
|
+
const threadId = pickString$1(message.params.threadId);
|
|
42373
|
+
if (threadId) return threadId;
|
|
42374
|
+
}
|
|
42375
|
+
const id = message.id;
|
|
42376
|
+
if (id === void 0) return;
|
|
42377
|
+
const pending = this.pendingRequests.get(String(id));
|
|
42378
|
+
return pending?.generation === generation ? pending.threadId : void 0;
|
|
42379
|
+
}
|
|
42380
|
+
routeMessage(generation, message) {
|
|
42381
|
+
if (!this.isCurrentGeneration(generation)) return;
|
|
42382
|
+
if (!isRecord$6(message)) throw new TypeError("Expected an app-server RPC object");
|
|
42383
|
+
const method = pickString$1(message.method);
|
|
42384
|
+
const id = message.id;
|
|
42385
|
+
if (method) {
|
|
42386
|
+
if (id !== void 0) this.routeServerRequest(generation, id, method, message.params);
|
|
42387
|
+
else this.routeNotification(method, message.params);
|
|
42388
|
+
return;
|
|
42389
|
+
}
|
|
42390
|
+
if (id === void 0) return;
|
|
42391
|
+
const key = String(id);
|
|
42392
|
+
const pending = this.pendingRequests.get(key);
|
|
42393
|
+
if (!pending || pending.generation !== generation) return;
|
|
42394
|
+
this.pendingRequests.delete(key);
|
|
42395
|
+
clearTimeout(pending.timeout);
|
|
42396
|
+
if (isRecord$6(message.error)) pending.reject(new CodexAppServerRpcError(pickString$1(message.error.message) ?? `Codex app-server request failed: ${pending.method}`, typeof message.error.code === "number" ? message.error.code : void 0, message.error.data, pending.method));
|
|
42397
|
+
else pending.resolve(message.result);
|
|
42398
|
+
}
|
|
42399
|
+
routeNotification(method, params) {
|
|
42400
|
+
const threadId = isRecord$6(params) ? pickString$1(params.threadId) : void 0;
|
|
42401
|
+
if (!threadId) return;
|
|
42402
|
+
for (const handler of this.notificationHandlers.get(threadId) ?? []) handler(method, params);
|
|
42403
|
+
}
|
|
42404
|
+
async routeServerRequest(generation, id, method, params) {
|
|
42405
|
+
const threadId = isRecord$6(params) ? pickString$1(params.threadId) : void 0;
|
|
42406
|
+
const handler = (threadId ? this.serverRequestHandlers.get(threadId) : void 0)?.values().next().value;
|
|
42407
|
+
let response;
|
|
42408
|
+
try {
|
|
42409
|
+
if (handler) response = {
|
|
42410
|
+
id,
|
|
42411
|
+
result: await handler(method, params)
|
|
42412
|
+
};
|
|
42413
|
+
else if (APPROVAL_REQUEST_METHODS.has(method)) response = {
|
|
42414
|
+
id,
|
|
42415
|
+
result: { decision: "cancel" }
|
|
42416
|
+
};
|
|
42417
|
+
else response = {
|
|
42418
|
+
error: {
|
|
42419
|
+
code: -32601,
|
|
42420
|
+
message: `Unsupported Codex app-server request: ${method}`
|
|
42421
|
+
},
|
|
42422
|
+
id
|
|
42423
|
+
};
|
|
42424
|
+
} catch (error) {
|
|
42425
|
+
response = {
|
|
42426
|
+
error: {
|
|
42427
|
+
code: -32603,
|
|
42428
|
+
message: error instanceof Error ? error.message : "Codex server request failed"
|
|
42429
|
+
},
|
|
42430
|
+
id
|
|
42431
|
+
};
|
|
42432
|
+
}
|
|
42433
|
+
if (!this.isCurrentGeneration(generation)) return;
|
|
42434
|
+
try {
|
|
42435
|
+
this.writeForGeneration(generation, response);
|
|
42436
|
+
} catch (error) {
|
|
42437
|
+
this.fail(this.toConnectionError(error, "Failed to write Codex app-server response"), generation);
|
|
42438
|
+
}
|
|
42439
|
+
}
|
|
42440
|
+
writeForGeneration(generation, message) {
|
|
42441
|
+
const processGeneration = this.processGeneration;
|
|
42442
|
+
if (!processGeneration || processGeneration.generation !== generation) throw new CodexAppServerConnectionError("Codex app-server stdin is unavailable");
|
|
42443
|
+
this.writeToProcess(processGeneration, message);
|
|
42444
|
+
}
|
|
42445
|
+
writeToProcess(processGeneration, message) {
|
|
42446
|
+
if (!this.ownsProcess(processGeneration) || !processGeneration.child.stdin) throw new CodexAppServerConnectionError("Codex app-server stdin is unavailable");
|
|
42447
|
+
processGeneration.child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
42448
|
+
}
|
|
42449
|
+
emitText(handlers, data) {
|
|
42450
|
+
for (const handler of handlers) handler(data);
|
|
42451
|
+
}
|
|
42452
|
+
removeHandler(map, key, handler) {
|
|
42453
|
+
const handlers = map.get(key);
|
|
42454
|
+
handlers?.delete(handler);
|
|
42455
|
+
if (handlers?.size === 0) map.delete(key);
|
|
42456
|
+
}
|
|
42457
|
+
fail(error, generation, reconnect = true) {
|
|
42458
|
+
if (!this.isCurrentGeneration(generation) || this.connectionError) return;
|
|
42459
|
+
this.connectionError = error;
|
|
42460
|
+
this.connected = false;
|
|
42461
|
+
if (this.connectionState?.generation === generation) this.connectionState = void 0;
|
|
42462
|
+
const processGeneration = this.processGeneration;
|
|
42463
|
+
if (processGeneration?.generation === generation) {
|
|
42464
|
+
this.processGeneration = void 0;
|
|
42465
|
+
this.terminateChild(processGeneration.child);
|
|
42466
|
+
}
|
|
42467
|
+
this.rejectPendingRequests(generation, error);
|
|
42468
|
+
this.emitDisconnect(error);
|
|
42469
|
+
if (reconnect) this.scheduleReconnect();
|
|
42470
|
+
}
|
|
42471
|
+
scheduleReconnect() {
|
|
42472
|
+
if (this.closedByHost || this.reconnectTimer || this.threadRegistrations.size === 0) return;
|
|
42473
|
+
const maxAttempts = Math.max(0, Math.floor(this.options.reconnectMaxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS));
|
|
42474
|
+
const epoch = this.reconnectEpoch;
|
|
42475
|
+
if (this.reconnectAttempt >= maxAttempts) {
|
|
42476
|
+
if (this.reconnectExhaustedEpoch === epoch) return;
|
|
42477
|
+
this.reconnectExhaustedEpoch = epoch;
|
|
42478
|
+
const error = new CodexAppServerConnectionError(`Codex app-server reconnect exhausted after ${maxAttempts} attempts`);
|
|
42479
|
+
this.notifyResumeErrors(error);
|
|
42480
|
+
return;
|
|
42481
|
+
}
|
|
42482
|
+
const baseDelay = this.options.reconnectBaseDelayMs ?? DEFAULT_RECONNECT_BASE_DELAY_MS;
|
|
42483
|
+
const maxDelay = this.options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
42484
|
+
const delay = Math.min(baseDelay * 2 ** this.reconnectAttempt, maxDelay);
|
|
42485
|
+
this.reconnectAttempt += 1;
|
|
42486
|
+
this.reconnectTimer = setTimeout(() => {
|
|
42487
|
+
this.reconnectTimer = void 0;
|
|
42488
|
+
if (epoch !== this.reconnectEpoch || this.closedByHost) return;
|
|
42489
|
+
this.startConnection().catch(() => {});
|
|
42490
|
+
}, delay);
|
|
42491
|
+
this.reconnectTimer.unref?.();
|
|
42492
|
+
}
|
|
42493
|
+
async resumeRegisteredThreads(generation) {
|
|
42494
|
+
await Promise.all([...this.threadRegistrations.values()].map(async ({ params, registrations }) => {
|
|
42495
|
+
try {
|
|
42496
|
+
const response = await this.requestForGeneration(generation, "thread/resume", params);
|
|
42497
|
+
if (!this.isCurrentGeneration(generation) || !this.connected) return;
|
|
42498
|
+
await Promise.allSettled([...registrations].map(({ onResume }) => onResume(response)));
|
|
42499
|
+
} catch (error) {
|
|
42500
|
+
if (!this.isCurrentGeneration(generation) || !this.connected) return;
|
|
42501
|
+
const resumeError = error instanceof Error ? error : new Error(String(error));
|
|
42502
|
+
await Promise.allSettled([...registrations].map(({ onResumeError }) => onResumeError(resumeError)));
|
|
42503
|
+
}
|
|
42504
|
+
}));
|
|
42505
|
+
}
|
|
42506
|
+
startUserRecoveryEpoch() {
|
|
42507
|
+
if (this.reconnectTimer) {
|
|
42508
|
+
clearTimeout(this.reconnectTimer);
|
|
42509
|
+
this.reconnectTimer = void 0;
|
|
42510
|
+
}
|
|
42511
|
+
this.reconnectAttempt = 0;
|
|
42512
|
+
this.reconnectEpoch += 1;
|
|
42513
|
+
this.reconnectExhaustedEpoch = void 0;
|
|
42514
|
+
}
|
|
42515
|
+
notifyResumeErrors(error) {
|
|
42516
|
+
Promise.allSettled([...this.threadRegistrations.values()].flatMap(({ registrations }) => [...registrations].map(({ onResumeError }) => onResumeError(error))));
|
|
42517
|
+
}
|
|
42518
|
+
rejectPendingRequests(generation, error) {
|
|
42519
|
+
for (const [key, pending] of this.pendingRequests) {
|
|
42520
|
+
if (pending.generation !== generation) continue;
|
|
42521
|
+
this.pendingRequests.delete(key);
|
|
42522
|
+
clearTimeout(pending.timeout);
|
|
42523
|
+
pending.reject(error);
|
|
42524
|
+
}
|
|
42525
|
+
}
|
|
42526
|
+
emitDisconnect(error) {
|
|
42527
|
+
for (const handler of this.disconnectHandlers) try {
|
|
42528
|
+
handler(error);
|
|
42529
|
+
} catch (handlerError) {
|
|
42530
|
+
console.error("Codex app-server disconnect handler failed:", handlerError);
|
|
42531
|
+
}
|
|
42532
|
+
}
|
|
42533
|
+
isCurrentGeneration(generation) {
|
|
42534
|
+
return !this.closedByHost && this.activeGeneration === generation;
|
|
42535
|
+
}
|
|
42536
|
+
ownsProcess(processGeneration) {
|
|
42537
|
+
return this.isCurrentGeneration(processGeneration.generation) && this.processGeneration === processGeneration;
|
|
42538
|
+
}
|
|
42539
|
+
toConnectionError(error, message) {
|
|
42540
|
+
if (error instanceof CodexAppServerRpcError || error instanceof CodexAppServerConnectionError) return error;
|
|
42541
|
+
return new CodexAppServerConnectionError(message, { cause: error });
|
|
42542
|
+
}
|
|
42543
|
+
terminateChild(child) {
|
|
42544
|
+
if (!child?.pid || child.killed) return;
|
|
42545
|
+
if (process.platform !== "win32") try {
|
|
42546
|
+
process.kill(-child.pid, "SIGTERM");
|
|
42547
|
+
return;
|
|
42548
|
+
} catch {}
|
|
42549
|
+
child.kill("SIGTERM");
|
|
42550
|
+
}
|
|
42551
|
+
};
|
|
42552
|
+
|
|
41670
42553
|
//#endregion
|
|
41671
42554
|
//#region node_modules/.pnpm/diff@8.0.4/node_modules/diff/libesm/diff/base.js
|
|
41672
42555
|
var Diff = class {
|
|
@@ -50954,7 +51837,7 @@ const buildSpawnArgs = (params) => {
|
|
|
50954
51837
|
default: throw new Error(`spawnAgent: unsupported agent type "${params.agentType}"`);
|
|
50955
51838
|
}
|
|
50956
51839
|
};
|
|
50957
|
-
const killProcessTree
|
|
51840
|
+
const killProcessTree = (proc, signal, detached) => {
|
|
50958
51841
|
if (!proc.pid || proc.killed) return;
|
|
50959
51842
|
if (platform() === "win32") {
|
|
50960
51843
|
try {
|
|
@@ -51357,7 +52240,7 @@ const spawnAgent = async (options) => {
|
|
|
51357
52240
|
exit,
|
|
51358
52241
|
kill: (signal = "SIGINT") => {
|
|
51359
52242
|
killedByUs = true;
|
|
51360
|
-
killProcessTree
|
|
52243
|
+
killProcessTree(proc, signal, detached);
|
|
51361
52244
|
},
|
|
51362
52245
|
pid: proc.pid,
|
|
51363
52246
|
get sessionId() {
|
|
@@ -248553,6 +249436,13 @@ const MAX_MISSED_HEARTBEATS = 3;
|
|
|
248553
249436
|
*/
|
|
248554
249437
|
const CONNECT_TIMEOUT = 15e3;
|
|
248555
249438
|
const METRICS_ACK_TIMEOUT_MS = 15e3;
|
|
249439
|
+
/**
|
|
249440
|
+
* Close reason the gateway sends when a newer socket with the same
|
|
249441
|
+
* `connectionId` takes over. `connectionId` is persisted per install, so this
|
|
249442
|
+
* is how a second process of the same install (two `lh connect` sharing one
|
|
249443
|
+
* home) shows up.
|
|
249444
|
+
*/
|
|
249445
|
+
const REPLACED_CLOSE_REASON = "Replaced by new connection";
|
|
248556
249446
|
const noopLogger = {
|
|
248557
249447
|
debug: () => {},
|
|
248558
249448
|
error: () => {},
|
|
@@ -248570,6 +249460,15 @@ var GatewayClient = class extends EventEmitter {
|
|
|
248570
249460
|
status = "disconnected";
|
|
248571
249461
|
pendingMetricAcks = /* @__PURE__ */ new Map();
|
|
248572
249462
|
intentionalDisconnect = false;
|
|
249463
|
+
/**
|
|
249464
|
+
* When each socket abandoned mid-handshake was abandoned. Only such a socket
|
|
249465
|
+
* can reach the gateway after its successor and knock it off with the
|
|
249466
|
+
* takeover reason: an opened socket was registered before the successor
|
|
249467
|
+
* existed. Each can do that at most once, and only within `connectTimeoutMs`
|
|
249468
|
+
* (the handshake budget), so a takeover consumes one live entry and anything
|
|
249469
|
+
* beyond them is another client.
|
|
249470
|
+
*/
|
|
249471
|
+
inFlightAbandons = [];
|
|
248573
249472
|
deviceId;
|
|
248574
249473
|
connectionId;
|
|
248575
249474
|
channel;
|
|
@@ -248848,6 +249747,13 @@ var GatewayClient = class extends EventEmitter {
|
|
|
248848
249747
|
this.rejectPendingMetricAcks();
|
|
248849
249748
|
this.clearConnectWatchdog();
|
|
248850
249749
|
this.ws = null;
|
|
249750
|
+
if (!this.intentionalDisconnect && reason.toString() === REPLACED_CLOSE_REASON && !this.consumeInFlightAbandon()) {
|
|
249751
|
+
this.logger.warn("Connection taken over by another client with the same connection id; not reconnecting");
|
|
249752
|
+
this.setStatus("disconnected");
|
|
249753
|
+
this.emit("replaced");
|
|
249754
|
+
this.emit("disconnected");
|
|
249755
|
+
return;
|
|
249756
|
+
}
|
|
248851
249757
|
if (!this.intentionalDisconnect && this.autoReconnect) {
|
|
248852
249758
|
this.setStatus("reconnecting");
|
|
248853
249759
|
this.scheduleReconnect();
|
|
@@ -248856,6 +249762,12 @@ var GatewayClient = class extends EventEmitter {
|
|
|
248856
249762
|
this.emit("disconnected");
|
|
248857
249763
|
}
|
|
248858
249764
|
};
|
|
249765
|
+
/** Attribute a takeover to one of our own late sockets, if one can still land. */
|
|
249766
|
+
consumeInFlightAbandon() {
|
|
249767
|
+
const now = Date.now();
|
|
249768
|
+
this.inFlightAbandons = this.inFlightAbandons.filter((at) => now - at <= this.connectTimeoutMs);
|
|
249769
|
+
return this.inFlightAbandons.shift() !== void 0;
|
|
249770
|
+
}
|
|
248859
249771
|
handleError = (error) => {
|
|
248860
249772
|
this.logger.error("WebSocket error:", error.message);
|
|
248861
249773
|
this.emit("error", error);
|
|
@@ -248945,6 +249857,7 @@ var GatewayClient = class extends EventEmitter {
|
|
|
248945
249857
|
this.rejectPendingMetricAcks();
|
|
248946
249858
|
if (!this.ws) return;
|
|
248947
249859
|
const ws = this.ws;
|
|
249860
|
+
if (ws.readyState === wrapper_default.CONNECTING) this.inFlightAbandons.push(Date.now());
|
|
248948
249861
|
const suppressCloseError = (error) => {
|
|
248949
249862
|
this.logger.debug(`Ignoring WebSocket error during close: ${error.message}`);
|
|
248950
249863
|
};
|
|
@@ -248992,6 +249905,95 @@ const getHeterogeneousTypeLabel = (type) => {
|
|
|
248992
249905
|
return Object.hasOwn(HETEROGENEOUS_TYPE_LABELS, type) ? HETEROGENEOUS_TYPE_LABELS[type] : type;
|
|
248993
249906
|
};
|
|
248994
249907
|
|
|
249908
|
+
//#endregion
|
|
249909
|
+
//#region ../../packages/heterogeneous-agents/src/models/codex.ts
|
|
249910
|
+
/** Discovery has no thread: provider overrides must be applied at process startup. */
|
|
249911
|
+
const catalogConfig = (source, initialCwd) => {
|
|
249912
|
+
const args = [];
|
|
249913
|
+
let cwd = initialCwd;
|
|
249914
|
+
const valueFlags = [
|
|
249915
|
+
"-c",
|
|
249916
|
+
"--config",
|
|
249917
|
+
"-C",
|
|
249918
|
+
"--cd",
|
|
249919
|
+
"--enable",
|
|
249920
|
+
"--disable"
|
|
249921
|
+
];
|
|
249922
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
249923
|
+
const arg = source[index];
|
|
249924
|
+
if (arg === "--") break;
|
|
249925
|
+
if ([
|
|
249926
|
+
"-p",
|
|
249927
|
+
"--profile",
|
|
249928
|
+
"--oss",
|
|
249929
|
+
"--local-provider",
|
|
249930
|
+
"--ignore-user-config"
|
|
249931
|
+
].some((flag) => arg === flag || arg.startsWith(`${flag}=`) || flag === "-p" && arg.startsWith("-p"))) throw Object.assign(/* @__PURE__ */ new Error("Unsupported Codex catalog configuration"), { code: "unsupported_configuration" });
|
|
249932
|
+
const flag = valueFlags.find((candidate) => arg === candidate || arg.startsWith(`${candidate}=`) || candidate.length === 2 && arg.startsWith(candidate));
|
|
249933
|
+
if (!flag) continue;
|
|
249934
|
+
const value = arg === flag ? source[++index] : arg.slice(flag.length).replace(/^=/, "");
|
|
249935
|
+
if (!value) throw new Error(`Missing value for Codex ${flag}`);
|
|
249936
|
+
if (flag === "-C" || flag === "--cd") cwd = path.resolve(initialCwd, value);
|
|
249937
|
+
else args.push(flag, value);
|
|
249938
|
+
}
|
|
249939
|
+
return {
|
|
249940
|
+
args,
|
|
249941
|
+
cwd
|
|
249942
|
+
};
|
|
249943
|
+
};
|
|
249944
|
+
/** A short-lived catalog connection, independent of the exec/app-server task transport. */
|
|
249945
|
+
const listCodexModels = async ({ args = [], commandPath, cwd, env, timeoutMs }) => {
|
|
249946
|
+
const client = new CodexAppServerClient({
|
|
249947
|
+
...catalogConfig(args, cwd),
|
|
249948
|
+
clientVersion: "1.0.0",
|
|
249949
|
+
commandPath,
|
|
249950
|
+
env,
|
|
249951
|
+
reconnectMaxAttempts: 0
|
|
249952
|
+
});
|
|
249953
|
+
let timer;
|
|
249954
|
+
const readPages = async () => {
|
|
249955
|
+
const models = /* @__PURE__ */ new Map();
|
|
249956
|
+
const cursors = /* @__PURE__ */ new Set();
|
|
249957
|
+
let cursor;
|
|
249958
|
+
do {
|
|
249959
|
+
const page = await client.request("model/list", {
|
|
249960
|
+
...cursor ? { cursor } : {},
|
|
249961
|
+
includeHidden: false,
|
|
249962
|
+
limit: 100
|
|
249963
|
+
});
|
|
249964
|
+
if (!isRecord$6(page) || !Array.isArray(page.data)) throw new Error("Invalid Codex model catalog");
|
|
249965
|
+
for (const item of page.data) {
|
|
249966
|
+
if (!isRecord$6(item) || typeof item.model !== "string" || !item.model.trim()) throw new Error("Invalid Codex model catalog entry");
|
|
249967
|
+
if (item.hidden === true || models.has(item.model)) continue;
|
|
249968
|
+
models.set(item.model, {
|
|
249969
|
+
id: item.model,
|
|
249970
|
+
...typeof item.displayName === "string" && item.displayName.trim() ? { label: item.displayName } : {},
|
|
249971
|
+
modelId: item.model,
|
|
249972
|
+
providerId: "codex"
|
|
249973
|
+
});
|
|
249974
|
+
}
|
|
249975
|
+
if (page.nextCursor == null) break;
|
|
249976
|
+
if (typeof page.nextCursor !== "string" || !page.nextCursor || cursors.has(page.nextCursor)) throw new Error("Invalid Codex model catalog cursor");
|
|
249977
|
+
cursor = page.nextCursor;
|
|
249978
|
+
cursors.add(cursor);
|
|
249979
|
+
} while (cursor);
|
|
249980
|
+
return [...models.values()];
|
|
249981
|
+
};
|
|
249982
|
+
try {
|
|
249983
|
+
return await Promise.race([readPages(), new Promise((_, reject) => {
|
|
249984
|
+
timer = setTimeout(() => {
|
|
249985
|
+
reject(Object.assign(/* @__PURE__ */ new Error("Codex model discovery timed out"), { code: "ETIMEDOUT" }));
|
|
249986
|
+
}, timeoutMs);
|
|
249987
|
+
})]);
|
|
249988
|
+
} catch (error) {
|
|
249989
|
+
if (error instanceof CodexAppServerRpcError && error.code === -32601) throw Object.assign(/* @__PURE__ */ new Error("Codex model discovery is not supported"), { code: "unsupported_client" });
|
|
249990
|
+
throw error;
|
|
249991
|
+
} finally {
|
|
249992
|
+
clearTimeout(timer);
|
|
249993
|
+
client.close();
|
|
249994
|
+
}
|
|
249995
|
+
};
|
|
249996
|
+
|
|
248995
249997
|
//#endregion
|
|
248996
249998
|
//#region ../../packages/heterogeneous-agents/src/models/listHeterogeneousAgentModels.ts
|
|
248997
249999
|
init_cliSpawn();
|
|
@@ -249181,8 +250183,11 @@ const parseQoderModelCatalog = (stdout) => {
|
|
|
249181
250183
|
};
|
|
249182
250184
|
const getErrorRecord = (error) => error;
|
|
249183
250185
|
const classifyCatalogError = (error) => {
|
|
250186
|
+
if (error instanceof Error && error.cause) return classifyCatalogError(error.cause);
|
|
249184
250187
|
const { code, killed, signal } = getErrorRecord(error);
|
|
249185
250188
|
if (code === "ENOENT") return "cli_not_found";
|
|
250189
|
+
if (code === "unsupported_client") return "unsupported_client";
|
|
250190
|
+
if (code === "unsupported_configuration") return "unsupported_configuration";
|
|
249186
250191
|
if (code === "ETIMEDOUT" || killed || signal === "SIGTERM") return "timeout";
|
|
249187
250192
|
return "command_failed";
|
|
249188
250193
|
};
|
|
@@ -249210,6 +250215,17 @@ const listHeterogeneousAgentModels = async (params) => {
|
|
|
249210
250215
|
...mergedPath ? { PATH: mergedPath } : {}
|
|
249211
250216
|
};
|
|
249212
250217
|
try {
|
|
250218
|
+
if (params.type === "codex") return {
|
|
250219
|
+
models: await listCodexModels({
|
|
250220
|
+
args: params.args,
|
|
250221
|
+
commandPath: resolved.command,
|
|
250222
|
+
cwd: params.cwd ?? process.cwd(),
|
|
250223
|
+
env,
|
|
250224
|
+
timeoutMs: MODEL_CATALOG_TIMEOUT_MS
|
|
250225
|
+
}),
|
|
250226
|
+
status: "success",
|
|
250227
|
+
updatedAt
|
|
250228
|
+
};
|
|
249213
250229
|
if (params.type === "droid") return {
|
|
249214
250230
|
models: await listDroidAcpModels({
|
|
249215
250231
|
args: params.args,
|
|
@@ -252822,7 +253838,7 @@ const getTargetFd = ({ value, optionName, fdNumber, direction }) => {
|
|
|
252822
253838
|
if (tty.isatty(targetFdNumber)) throw new TypeError(`The \`${optionName}: ${serializeOptionValue(value)}\` option is invalid: it cannot be a TTY with synchronous methods.`);
|
|
252823
253839
|
return {
|
|
252824
253840
|
type: "uint8Array",
|
|
252825
|
-
value: bufferToUint8Array(readFileSync
|
|
253841
|
+
value: bufferToUint8Array(readFileSync(targetFdNumber)),
|
|
252826
253842
|
optionName
|
|
252827
253843
|
};
|
|
252828
253844
|
};
|
|
@@ -253103,8 +254119,8 @@ const addProperties$1 = {
|
|
|
253103
254119
|
const addPropertiesSync = {
|
|
253104
254120
|
input: {
|
|
253105
254121
|
...addProperties$1,
|
|
253106
|
-
fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync
|
|
253107
|
-
filePath: ({ value: { file } }) => ({ contents: [bufferToUint8Array(readFileSync
|
|
254122
|
+
fileUrl: ({ value }) => ({ contents: [bufferToUint8Array(readFileSync(value))] }),
|
|
254123
|
+
filePath: ({ value: { file } }) => ({ contents: [bufferToUint8Array(readFileSync(file))] }),
|
|
253108
254124
|
fileNumber: forbiddenIfSync,
|
|
253109
254125
|
iterable: ({ value }) => ({ contents: [...value] }),
|
|
253110
254126
|
string: ({ value }) => ({ contents: [value] }),
|
|
@@ -256682,65 +257698,6 @@ const createFileSearchModule = (toolDetector) => {
|
|
|
256682
257698
|
}
|
|
256683
257699
|
};
|
|
256684
257700
|
|
|
256685
|
-
//#endregion
|
|
256686
|
-
//#region ../../packages/local-file-shell/src/shell/clixml.ts
|
|
256687
|
-
/**
|
|
256688
|
-
* Decode PowerShell CLIXML blocks embedded in redirected stderr.
|
|
256689
|
-
*
|
|
256690
|
-
* When powershell.exe / pwsh.exe runs with redirected stderr, records from its
|
|
256691
|
-
* non-stdout streams (error, warning, verbose, information / Write-Host) are
|
|
256692
|
-
* serialized to stderr as CLIXML: `#< CLIXML\n<Objs ...>...</Objs>`. Shown
|
|
256693
|
-
* verbatim this is unreadable noise for the model and the user, so we extract
|
|
256694
|
-
* the human-readable messages and replace each block in place — the same
|
|
256695
|
-
* approach Ansible's `_parse_clixml` takes.
|
|
256696
|
-
*/
|
|
256697
|
-
const CLIXML_MARKER = "#< CLIXML";
|
|
256698
|
-
/** CLIXML escapes characters as `_xHHHH_` (UTF-16 code units). */
|
|
256699
|
-
const unescapeClixmlText = (text) => text.replaceAll(/_x([\dA-Fa-f]{4})_/g, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))).replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("&", "&");
|
|
256700
|
-
/** Stream-typed string records: `<S S="Error">text</S>` (PS 5.1 style). */
|
|
256701
|
-
const STREAM_ENTRY = /<S S="(?:Error|Warning|Verbose|Debug|Info)">([\S\s]*?)<\/S>/g;
|
|
256702
|
-
/**
|
|
256703
|
-
* Rendered text of serialized records (`InformationRecord` / `HostInformationMessage`
|
|
256704
|
-
* from Write-Host end up here). Nested objects repeat the same message in their
|
|
256705
|
-
* own `<ToString>`, so consecutive duplicates are collapsed by the caller.
|
|
256706
|
-
*/
|
|
256707
|
-
const TO_STRING_ENTRY = /<ToString>([\S\s]*?)<\/ToString>/g;
|
|
256708
|
-
const extractBlockMessages = (block) => {
|
|
256709
|
-
const streamTexts = [...block.matchAll(STREAM_ENTRY)].map((m) => m[1]);
|
|
256710
|
-
const texts = streamTexts.length > 0 ? streamTexts : [...block.matchAll(TO_STRING_ENTRY)].map((m) => m[1]);
|
|
256711
|
-
const messages = [];
|
|
256712
|
-
for (const raw of texts) {
|
|
256713
|
-
const text = unescapeClixmlText(raw);
|
|
256714
|
-
if (messages.at(-1) === text) continue;
|
|
256715
|
-
messages.push(text);
|
|
256716
|
-
}
|
|
256717
|
-
return messages;
|
|
256718
|
-
};
|
|
256719
|
-
/**
|
|
256720
|
-
* Replace every CLIXML block in `text` with its decoded messages. Plain text
|
|
256721
|
-
* around the blocks is preserved; input without a marker is returned as-is.
|
|
256722
|
-
* Truncated blocks (output preview cuts) decode whatever entries survived.
|
|
256723
|
-
*/
|
|
256724
|
-
const decodeClixml = (text) => {
|
|
256725
|
-
if (!text.includes(CLIXML_MARKER)) return text;
|
|
256726
|
-
let result = "";
|
|
256727
|
-
let cursor = 0;
|
|
256728
|
-
while (cursor < text.length) {
|
|
256729
|
-
const markerIndex = text.indexOf(CLIXML_MARKER, cursor);
|
|
256730
|
-
if (markerIndex === -1) {
|
|
256731
|
-
result += text.slice(cursor);
|
|
256732
|
-
break;
|
|
256733
|
-
}
|
|
256734
|
-
result += text.slice(cursor, markerIndex);
|
|
256735
|
-
const closeIndex = text.indexOf("</Objs>", markerIndex);
|
|
256736
|
-
const blockEnd = closeIndex === -1 ? text.length : closeIndex + 7;
|
|
256737
|
-
const messages = extractBlockMessages(text.slice(markerIndex, blockEnd));
|
|
256738
|
-
if (messages.length > 0) result += messages.map((m) => m.endsWith("\n") ? m : `${m}\n`).join("");
|
|
256739
|
-
cursor = blockEnd;
|
|
256740
|
-
}
|
|
256741
|
-
return result;
|
|
256742
|
-
};
|
|
256743
|
-
|
|
256744
257701
|
//#endregion
|
|
256745
257702
|
//#region node_modules/.pnpm/tree-kill@1.2.2/node_modules/tree-kill/index.js
|
|
256746
257703
|
var require_tree_kill = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -256838,6 +257795,118 @@ var require_tree_kill = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
256838
257795
|
}
|
|
256839
257796
|
}));
|
|
256840
257797
|
|
|
257798
|
+
//#endregion
|
|
257799
|
+
//#region ../../packages/local-file-shell/src/shell/child-process-backend.ts
|
|
257800
|
+
var import_tree_kill = /* @__PURE__ */ __toESM(require_tree_kill());
|
|
257801
|
+
const KILL_SIGNAL = "SIGKILL";
|
|
257802
|
+
/**
|
|
257803
|
+
* Non-interactive backend: a plain `child_process` with stdin left as an
|
|
257804
|
+
* unused pipe and stdout/stderr inherited straight into the output files.
|
|
257805
|
+
*/
|
|
257806
|
+
var ChildProcessBackend = class {
|
|
257807
|
+
capabilities = {
|
|
257808
|
+
interactive: false,
|
|
257809
|
+
persistent: false,
|
|
257810
|
+
streaming: false
|
|
257811
|
+
};
|
|
257812
|
+
name = "child-process";
|
|
257813
|
+
processes = /* @__PURE__ */ new Map();
|
|
257814
|
+
/**
|
|
257815
|
+
* @param spawnProcess - spawn implementation; a caller can pass one that
|
|
257816
|
+
* registers each process with a tracker. Defaults to `child_process.spawn`.
|
|
257817
|
+
*/
|
|
257818
|
+
constructor(spawnProcess = spawn) {
|
|
257819
|
+
this.spawnProcess = spawnProcess;
|
|
257820
|
+
}
|
|
257821
|
+
spawn({ cmd, args }, { cwd, env, outputFiles, shellId }) {
|
|
257822
|
+
const childProcess = this.spawnProcess(cmd, args, {
|
|
257823
|
+
cwd,
|
|
257824
|
+
detached: process.platform !== "win32",
|
|
257825
|
+
env,
|
|
257826
|
+
shell: false,
|
|
257827
|
+
stdio: [
|
|
257828
|
+
"pipe",
|
|
257829
|
+
outputFiles.stdout.fd,
|
|
257830
|
+
outputFiles.stderr.fd
|
|
257831
|
+
],
|
|
257832
|
+
windowsHide: true
|
|
257833
|
+
});
|
|
257834
|
+
this.processes.set(shellId, childProcess);
|
|
257835
|
+
return childProcess;
|
|
257836
|
+
}
|
|
257837
|
+
kill(shellId) {
|
|
257838
|
+
const childProcess = this.processes.get(shellId);
|
|
257839
|
+
if (!childProcess) throw new Error(`Shell ID ${shellId} not found`);
|
|
257840
|
+
if (childProcess.pid) {
|
|
257841
|
+
(0, import_tree_kill.default)(childProcess.pid, KILL_SIGNAL);
|
|
257842
|
+
return;
|
|
257843
|
+
}
|
|
257844
|
+
childProcess.kill(KILL_SIGNAL);
|
|
257845
|
+
}
|
|
257846
|
+
release(shellId) {
|
|
257847
|
+
this.processes.delete(shellId);
|
|
257848
|
+
}
|
|
257849
|
+
};
|
|
257850
|
+
|
|
257851
|
+
//#endregion
|
|
257852
|
+
//#region ../../packages/local-file-shell/src/shell/clixml.ts
|
|
257853
|
+
/**
|
|
257854
|
+
* Decode PowerShell CLIXML blocks embedded in redirected stderr.
|
|
257855
|
+
*
|
|
257856
|
+
* When powershell.exe / pwsh.exe runs with redirected stderr, records from its
|
|
257857
|
+
* non-stdout streams (error, warning, verbose, information / Write-Host) are
|
|
257858
|
+
* serialized to stderr as CLIXML: `#< CLIXML\n<Objs ...>...</Objs>`. Shown
|
|
257859
|
+
* verbatim this is unreadable noise for the model and the user, so we extract
|
|
257860
|
+
* the human-readable messages and replace each block in place — the same
|
|
257861
|
+
* approach Ansible's `_parse_clixml` takes.
|
|
257862
|
+
*/
|
|
257863
|
+
const CLIXML_MARKER = "#< CLIXML";
|
|
257864
|
+
/** CLIXML escapes characters as `_xHHHH_` (UTF-16 code units). */
|
|
257865
|
+
const unescapeClixmlText = (text) => text.replaceAll(/_x([\dA-Fa-f]{4})_/g, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))).replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("&", "&");
|
|
257866
|
+
/** Stream-typed string records: `<S S="Error">text</S>` (PS 5.1 style). */
|
|
257867
|
+
const STREAM_ENTRY = /<S S="(?:Error|Warning|Verbose|Debug|Info)">([\S\s]*?)<\/S>/g;
|
|
257868
|
+
/**
|
|
257869
|
+
* Rendered text of serialized records (`InformationRecord` / `HostInformationMessage`
|
|
257870
|
+
* from Write-Host end up here). Nested objects repeat the same message in their
|
|
257871
|
+
* own `<ToString>`, so consecutive duplicates are collapsed by the caller.
|
|
257872
|
+
*/
|
|
257873
|
+
const TO_STRING_ENTRY = /<ToString>([\S\s]*?)<\/ToString>/g;
|
|
257874
|
+
const extractBlockMessages = (block) => {
|
|
257875
|
+
const streamTexts = [...block.matchAll(STREAM_ENTRY)].map((m) => m[1]);
|
|
257876
|
+
const texts = streamTexts.length > 0 ? streamTexts : [...block.matchAll(TO_STRING_ENTRY)].map((m) => m[1]);
|
|
257877
|
+
const messages = [];
|
|
257878
|
+
for (const raw of texts) {
|
|
257879
|
+
const text = unescapeClixmlText(raw);
|
|
257880
|
+
if (messages.at(-1) === text) continue;
|
|
257881
|
+
messages.push(text);
|
|
257882
|
+
}
|
|
257883
|
+
return messages;
|
|
257884
|
+
};
|
|
257885
|
+
/**
|
|
257886
|
+
* Replace every CLIXML block in `text` with its decoded messages. Plain text
|
|
257887
|
+
* around the blocks is preserved; input without a marker is returned as-is.
|
|
257888
|
+
* Truncated blocks (output preview cuts) decode whatever entries survived.
|
|
257889
|
+
*/
|
|
257890
|
+
const decodeClixml = (text) => {
|
|
257891
|
+
if (!text.includes(CLIXML_MARKER)) return text;
|
|
257892
|
+
let result = "";
|
|
257893
|
+
let cursor = 0;
|
|
257894
|
+
while (cursor < text.length) {
|
|
257895
|
+
const markerIndex = text.indexOf(CLIXML_MARKER, cursor);
|
|
257896
|
+
if (markerIndex === -1) {
|
|
257897
|
+
result += text.slice(cursor);
|
|
257898
|
+
break;
|
|
257899
|
+
}
|
|
257900
|
+
result += text.slice(cursor, markerIndex);
|
|
257901
|
+
const closeIndex = text.indexOf("</Objs>", markerIndex);
|
|
257902
|
+
const blockEnd = closeIndex === -1 ? text.length : closeIndex + 7;
|
|
257903
|
+
const messages = extractBlockMessages(text.slice(markerIndex, blockEnd));
|
|
257904
|
+
if (messages.length > 0) result += messages.map((m) => m.endsWith("\n") ? m : `${m}\n`).join("");
|
|
257905
|
+
cursor = blockEnd;
|
|
257906
|
+
}
|
|
257907
|
+
return result;
|
|
257908
|
+
};
|
|
257909
|
+
|
|
256841
257910
|
//#endregion
|
|
256842
257911
|
//#region ../../packages/local-file-shell/src/shell/utils.ts
|
|
256843
257912
|
/** Maximum preview bytes returned inline to prevent context explosion */
|
|
@@ -257287,7 +258356,6 @@ if (-not \$__lobeExecOk) {
|
|
|
257287
258356
|
|
|
257288
258357
|
//#endregion
|
|
257289
258358
|
//#region ../../packages/local-file-shell/src/shell/process-manager.ts
|
|
257290
|
-
var import_tree_kill = /* @__PURE__ */ __toESM(require_tree_kill());
|
|
257291
258359
|
const DEFAULT_OBSERVATION_TIMEOUT_MS = 6e4;
|
|
257292
258360
|
/**
|
|
257293
258361
|
* Ceiling on a single observation window. Sized for the real unit of work an
|
|
@@ -257336,7 +258404,6 @@ const GET_COMMAND_OUTPUT_HEAD_RATIO = 0;
|
|
|
257336
258404
|
const OUTPUT_PREVIEW_TOTAL_MAX_BYTES = 22 * 1024;
|
|
257337
258405
|
const OUTPUT_PREVIEW_STREAM_MAX_BYTES = 18 * 1024;
|
|
257338
258406
|
const OUTPUT_PREVIEW_SECONDARY_MIN_BYTES = 4 * 1024;
|
|
257339
|
-
const KILL_SIGNAL = "SIGKILL";
|
|
257340
258407
|
/**
|
|
257341
258408
|
* Not-found text for a shell id this process never issued. Shell ids travel
|
|
257342
258409
|
* through the model and the device gateway, so a lookup can land on a device
|
|
@@ -257346,6 +258413,8 @@ const KILL_SIGNAL = "SIGKILL";
|
|
|
257346
258413
|
*/
|
|
257347
258414
|
const shellNotFoundError = (shellId) => `Shell ID ${shellId} not found in this device process. It may have been started by a different or restarted process; its output is not available here.`;
|
|
257348
258415
|
var ShellProcessManager = class {
|
|
258416
|
+
/** Default backend for commands spawned through this manager. */
|
|
258417
|
+
backend;
|
|
257349
258418
|
nextShellId = 1;
|
|
257350
258419
|
/**
|
|
257351
258420
|
* Per-instance random prefix for shell ids. A bare counter restarts at 1 in
|
|
@@ -257356,7 +258425,8 @@ var ShellProcessManager = class {
|
|
|
257356
258425
|
shellIdToken = randomBytes(3).toString("hex");
|
|
257357
258426
|
outputRunDir;
|
|
257358
258427
|
processes = /* @__PURE__ */ new Map();
|
|
257359
|
-
constructor(outputRoot) {
|
|
258428
|
+
constructor(outputRoot, backend = new ChildProcessBackend()) {
|
|
258429
|
+
this.backend = backend;
|
|
257360
258430
|
const date = /* @__PURE__ */ new Date();
|
|
257361
258431
|
this.outputRunDir = path.join(path.resolve(outputRoot ?? path.join(os.tmpdir(), "lobehub", "shell")), `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`, process.pid.toString());
|
|
257362
258432
|
fs.mkdirSync(this.outputRunDir, {
|
|
@@ -257424,9 +258494,9 @@ var ShellProcessManager = class {
|
|
|
257424
258494
|
stdout: "",
|
|
257425
258495
|
success: false
|
|
257426
258496
|
};
|
|
257427
|
-
const { process:
|
|
257428
|
-
let exitCode =
|
|
257429
|
-
if (exitCode === null &&
|
|
258497
|
+
const { process: handle } = shellProcess;
|
|
258498
|
+
let exitCode = handle.exitCode ?? shellProcess.exitCode;
|
|
258499
|
+
if (exitCode === null && handle.signalCode == null) {
|
|
257430
258500
|
const waitTimeout = resolveWaitTimeout(typeof timeout === "number" && Number.isFinite(timeout) ? Math.min(Math.max(Math.trunc(timeout), 0), MAX_OBSERVATION_TIMEOUT_MS) : DEFAULT_OBSERVATION_TIMEOUT_MS);
|
|
257431
258501
|
if (waitTimeout > 0) {
|
|
257432
258502
|
let timer;
|
|
@@ -257436,11 +258506,11 @@ var ShellProcessManager = class {
|
|
|
257436
258506
|
await Promise.race([
|
|
257437
258507
|
new Promise((resolve) => {
|
|
257438
258508
|
onError = resolve;
|
|
257439
|
-
|
|
258509
|
+
handle.once("error", onError);
|
|
257440
258510
|
}),
|
|
257441
258511
|
new Promise((resolve) => {
|
|
257442
258512
|
onExit = resolve;
|
|
257443
|
-
|
|
258513
|
+
handle.once("exit", onExit);
|
|
257444
258514
|
}),
|
|
257445
258515
|
new Promise((resolve) => {
|
|
257446
258516
|
timer = setTimeout(resolve, waitTimeout);
|
|
@@ -257448,12 +258518,12 @@ var ShellProcessManager = class {
|
|
|
257448
258518
|
]);
|
|
257449
258519
|
} finally {
|
|
257450
258520
|
if (timer) clearTimeout(timer);
|
|
257451
|
-
if (onError)
|
|
257452
|
-
if (onExit)
|
|
258521
|
+
if (onError) handle.off("error", onError);
|
|
258522
|
+
if (onExit) handle.off("exit", onExit);
|
|
257453
258523
|
}
|
|
257454
258524
|
}
|
|
257455
258525
|
}
|
|
257456
|
-
exitCode =
|
|
258526
|
+
exitCode = handle.exitCode ?? shellProcess.exitCode;
|
|
257457
258527
|
if (exitCode !== null) {
|
|
257458
258528
|
shellProcess.endedAt ??= Date.now();
|
|
257459
258529
|
await shellProcess.closed;
|
|
@@ -257472,7 +258542,7 @@ var ShellProcessManager = class {
|
|
|
257472
258542
|
} catch {}
|
|
257473
258543
|
const startedAt = shellProcess.startedAt ?? Date.now();
|
|
257474
258544
|
const durationMs = Math.max(0, (shellProcess.endedAt ?? Date.now()) - startedAt);
|
|
257475
|
-
const signal =
|
|
258545
|
+
const signal = handle.signalCode ?? void 0;
|
|
257476
258546
|
const running = exitCode === null && !signal && !shellProcess.spawnError;
|
|
257477
258547
|
return {
|
|
257478
258548
|
duration_ms: durationMs,
|
|
@@ -257505,7 +258575,7 @@ var ShellProcessManager = class {
|
|
|
257505
258575
|
success: false
|
|
257506
258576
|
};
|
|
257507
258577
|
try {
|
|
257508
|
-
|
|
258578
|
+
(shellProcess.backend ?? this.backend).kill(shell_id);
|
|
257509
258579
|
return { success: true };
|
|
257510
258580
|
} catch (error) {
|
|
257511
258581
|
return {
|
|
@@ -257516,9 +258586,11 @@ var ShellProcessManager = class {
|
|
|
257516
258586
|
}
|
|
257517
258587
|
cleanupAll() {
|
|
257518
258588
|
for (const [id, sp] of this.processes) {
|
|
258589
|
+
const backend = sp.backend ?? this.backend;
|
|
257519
258590
|
try {
|
|
257520
|
-
|
|
258591
|
+
backend.kill(id);
|
|
257521
258592
|
} catch {}
|
|
258593
|
+
backend.release?.(id);
|
|
257522
258594
|
this.closeOutputFiles(sp.outputFiles);
|
|
257523
258595
|
this.processes.delete(id);
|
|
257524
258596
|
}
|
|
@@ -257544,14 +258616,6 @@ var ShellProcessManager = class {
|
|
|
257544
258616
|
};
|
|
257545
258617
|
}
|
|
257546
258618
|
};
|
|
257547
|
-
const killProcessTree = (childProcess) => {
|
|
257548
|
-
const { pid } = childProcess;
|
|
257549
|
-
if (pid) {
|
|
257550
|
-
(0, import_tree_kill.default)(pid, KILL_SIGNAL);
|
|
257551
|
-
return;
|
|
257552
|
-
}
|
|
257553
|
-
childProcess.kill(KILL_SIGNAL);
|
|
257554
|
-
};
|
|
257555
258619
|
const allocateOutputPreviewBytes = (stdoutSize, stderrSize) => {
|
|
257556
258620
|
if (stdoutSize + stderrSize <= OUTPUT_PREVIEW_TOTAL_MAX_BYTES) return {
|
|
257557
258621
|
stderr: stderrSize,
|
|
@@ -279889,12 +280953,12 @@ function writeTrustBundle(dir, caCertPem, extraCaCertPaths) {
|
|
|
279889
280953
|
const parts = [caCertPem.trim(), ...rootCertificates];
|
|
279890
280954
|
const extra = process.env.NODE_EXTRA_CA_CERTS;
|
|
279891
280955
|
if (extra) try {
|
|
279892
|
-
parts.push(readFileSync
|
|
280956
|
+
parts.push(readFileSync(extra, "utf8").trim());
|
|
279893
280957
|
} catch {}
|
|
279894
280958
|
for (const extraPath of extraCaCertPaths ?? []) {
|
|
279895
280959
|
let raw;
|
|
279896
280960
|
try {
|
|
279897
|
-
raw = readFileSync
|
|
280961
|
+
raw = readFileSync(extraPath, "utf8");
|
|
279898
280962
|
} catch (err) {
|
|
279899
280963
|
logForDebugging(`[mitm-ca] extraCaCertPaths: cannot read ${extraPath} (${err.code ?? String(err)}); skipping`, { level: "warn" });
|
|
279900
280964
|
continue;
|
|
@@ -280039,7 +281103,7 @@ function generateEphemeralCA(extraCaCertPaths) {
|
|
|
280039
281103
|
function readPem(path, label, field) {
|
|
280040
281104
|
let pem;
|
|
280041
281105
|
try {
|
|
280042
|
-
pem = readFileSync
|
|
281106
|
+
pem = readFileSync(path, "utf8");
|
|
280043
281107
|
} catch (err) {
|
|
280044
281108
|
const code = err.code ?? String(err);
|
|
280045
281109
|
throw new Error(`${field}: cannot read ${path} (${code})`);
|
|
@@ -290697,11 +291761,12 @@ const checkWorkingDirectory = async (cwd) => {
|
|
|
290697
291761
|
return `Working directory does not exist on ${os.hostname()}: ${cwd}. The shell is fine — run the command from a directory that exists on this device.`;
|
|
290698
291762
|
}
|
|
290699
291763
|
};
|
|
290700
|
-
async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_background, timeout = DEFAULT_OBSERVATION_TIMEOUT_MS }, { processManager, logger, onSandboxUnavailable, sandboxPolicy, spawnProcess
|
|
291764
|
+
async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_background, timeout = DEFAULT_OBSERVATION_TIMEOUT_MS }, { backend: backendOverride, processManager, logger, onSandboxUnavailable, sandboxPolicy, spawnProcess }) {
|
|
290701
291765
|
if (!command) return {
|
|
290702
291766
|
error: "command is required",
|
|
290703
291767
|
success: false
|
|
290704
291768
|
};
|
|
291769
|
+
const backend = backendOverride ?? (spawnProcess ? new ChildProcessBackend(spawnProcess) : processManager.backend);
|
|
290705
291770
|
const logPrefix = `[runCommand: ${description || command.slice(0, 50)}]`;
|
|
290706
291771
|
logger?.debug(`${logPrefix} Starting`, {
|
|
290707
291772
|
background: run_in_background,
|
|
@@ -290748,37 +291813,35 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
290748
291813
|
const shellId = processManager.createShellId();
|
|
290749
291814
|
const shellOutputFiles = processManager.createOutputFiles(shellId);
|
|
290750
291815
|
outputFiles = shellOutputFiles;
|
|
290751
|
-
const
|
|
291816
|
+
const handle = backend.spawn({
|
|
291817
|
+
args: launchCommand.args,
|
|
291818
|
+
cmd: launchCommand.cmd
|
|
291819
|
+
}, {
|
|
290752
291820
|
cwd,
|
|
290753
|
-
detached: process.platform !== "win32",
|
|
290754
291821
|
env: launchEnv,
|
|
290755
|
-
|
|
290756
|
-
|
|
290757
|
-
"pipe",
|
|
290758
|
-
shellOutputFiles.stdout.fd,
|
|
290759
|
-
shellOutputFiles.stderr.fd
|
|
290760
|
-
],
|
|
290761
|
-
windowsHide: true
|
|
291822
|
+
outputFiles: shellOutputFiles,
|
|
291823
|
+
shellId
|
|
290762
291824
|
});
|
|
290763
291825
|
const shellProcess = {
|
|
291826
|
+
backend,
|
|
290764
291827
|
exitCode: null,
|
|
290765
291828
|
outputFiles: shellOutputFiles,
|
|
290766
|
-
process:
|
|
291829
|
+
process: handle
|
|
290767
291830
|
};
|
|
290768
|
-
|
|
291831
|
+
handle.on("exit", (code) => {
|
|
290769
291832
|
logger?.debug(`${logPrefix} Process exited`, {
|
|
290770
291833
|
code,
|
|
290771
291834
|
shellId
|
|
290772
291835
|
});
|
|
290773
291836
|
shellProcess.exitCode = code ?? 0;
|
|
290774
291837
|
});
|
|
290775
|
-
|
|
291838
|
+
handle.on("error", (error) => {
|
|
290776
291839
|
logger?.error(`${logPrefix} Command failed:`, error);
|
|
290777
291840
|
const cwdContext = cwd ? ` (working directory: ${cwd})` : "";
|
|
290778
291841
|
shellProcess.spawnError = new Error(`Failed to start command${cwdContext}: ${error.message}`, { cause: error });
|
|
290779
291842
|
shellProcess.exitCode = 1;
|
|
290780
291843
|
});
|
|
290781
|
-
|
|
291844
|
+
handle.once("close", () => releaseSandbox?.());
|
|
290782
291845
|
processManager.register(shellId, shellProcess);
|
|
290783
291846
|
processManager.closeOutputFiles(shellOutputFiles);
|
|
290784
291847
|
logger?.info?.(`${logPrefix} Started session`, {
|
|
@@ -504097,6 +505160,10 @@ async function runConnect(options, isDaemonChild) {
|
|
|
504097
505160
|
error(`Could not refresh workspace ${wsId} connect token. Closing share connection.`);
|
|
504098
505161
|
closeWorkspaceConnection(wsId);
|
|
504099
505162
|
});
|
|
505163
|
+
wsClient.on("replaced", () => {
|
|
505164
|
+
error(`Workspace ${wsId} share connection was taken over by another '${"lh"} connect' on this machine. Closing it here.`);
|
|
505165
|
+
closeWorkspaceConnection(wsId);
|
|
505166
|
+
});
|
|
504100
505167
|
wsClient.on("auth_failed", (reason) => {
|
|
504101
505168
|
error(`Workspace ${wsId} authentication failed: ${reason}. Closing share connection.`);
|
|
504102
505169
|
closeWorkspaceConnection(wsId);
|
|
@@ -504202,6 +505269,11 @@ async function runConnect(options, isDaemonChild) {
|
|
|
504202
505269
|
cleanup();
|
|
504203
505270
|
process.exit(1);
|
|
504204
505271
|
});
|
|
505272
|
+
client.on("replaced", () => {
|
|
505273
|
+
error(`Another '${"lh"} connect' on this machine took over this device's gateway connection. Only one can be connected at a time; stopping this one.`);
|
|
505274
|
+
cleanup();
|
|
505275
|
+
process.exit(1);
|
|
505276
|
+
});
|
|
504205
505277
|
client.on("error", (err) => {
|
|
504206
505278
|
error(`Connection error: ${err.message}`);
|
|
504207
505279
|
});
|
|
@@ -509628,7 +510700,20 @@ const hasOperationToken = () => {
|
|
|
509628
510700
|
};
|
|
509629
510701
|
function registerGoalCommand$1(program) {
|
|
509630
510702
|
const goal = program.command("goal").description("Run long-horizon Goal Graphs");
|
|
509631
|
-
goal.command("
|
|
510703
|
+
goal.command("wake <id>").description("Deliver an external result to a specific Goal wait").requiredOption("--token <token>", "managerState.token from the waiting Goal").requiredOption("--event <id>", "Stable delivery ID").requiredOption("--type <type>", "Event type declared by the wait").requiredOption("--key <key>", "Event correlation key declared by the wait").option("--summary <text>", "Short observation").option("--reference <reference>", "Evidence reference, not an inline dataset").option("--json", "Output JSON").action(async (id, options) => {
|
|
510704
|
+
const result = await (await getTrpcClient()).goal.wake.mutate({
|
|
510705
|
+
id,
|
|
510706
|
+
waitToken: options.token,
|
|
510707
|
+
eventId: options.event,
|
|
510708
|
+
type: options.type,
|
|
510709
|
+
key: options.key,
|
|
510710
|
+
summary: options.summary,
|
|
510711
|
+
reference: options.reference
|
|
510712
|
+
});
|
|
510713
|
+
if (options.json) outputJson(result.data);
|
|
510714
|
+
else console.log(result.data.accepted ? "Event recorded; Goal continuation queued." : `Event not applied: ${result.data.reason}`);
|
|
510715
|
+
});
|
|
510716
|
+
goal.command("plan <id>").description("Atomically submit the current main Agent plan").requiredOption("--file <path>", "JSON plan: action tasks/wait/verify/retry/escalate, reason and action fields").requiredOption("--token <token>", "Current server-issued planning turn token").option("--operation <id>", "Defaults to LOBEHUB_OPERATION_ID").option("--json", "Output JSON").action(async (id, options) => {
|
|
509632
510717
|
const operationId = options.operation ?? process.env.LOBEHUB_OPERATION_ID;
|
|
509633
510718
|
if (!operationId) throw new Error("Current manager operation ID required");
|
|
509634
510719
|
const client = await getTrpcClient();
|
|
@@ -509656,31 +510741,31 @@ function registerGoalCommand$1(program) {
|
|
|
509656
510741
|
if (options.json) outputJson(result.data);
|
|
509657
510742
|
else console.log("Goal report recorded.");
|
|
509658
510743
|
});
|
|
509659
|
-
goal.command("create <title>").option("--max-manager-turns <n>", "Add a main Agent with this turn limit; it takes over problems the system planner cannot route").description("Create a standalone goal and seed its graph").option("-r, --requirement <text>", "Acceptance requirement").option("-i, --instruction <text>", "The ask in the user's own words, shown on the problem node").option("-t, --task <title...>", "Initial task node titles (omit to let the planner decompose)").option("--agent <id>", "Goal agent: supervises and plans the goal (defaults to the current agent)").option("--task-agent <id>", "Dedicated executor for the goal's tasks (defaults to the goal agent)").option("--project <id>", "Project ID").option("--explore <instruction>", "Explore alternatives using completed experiment results").option("--max-experiments <n>", "Maximum experiment nodes (requires --explore, default 10)").option("--
|
|
510744
|
+
goal.command("create <title>").option("--max-manager-turns <n>", "Add a main Agent with this turn limit; it takes over problems the system planner cannot route").description("Create a standalone goal and seed its graph").option("-r, --requirement <text>", "Acceptance requirement").option("-i, --instruction <text>", "The ask in the user's own words, shown on the problem node").option("-t, --task <title...>", "Initial task node titles (omit to let the planner decompose)").option("--agent <id>", "Goal agent: supervises and plans the goal (defaults to the current agent)").option("--task-agent <id>", "Dedicated executor for the goal's tasks (defaults to the goal agent)").option("--project <id>", "Project ID").option("--explore <instruction>", "Explore alternatives using completed experiment results").option("--max-experiments <n>", "Maximum experiment nodes (requires --explore, default 10)").option("--max-supervision-incidents <n>", "Maximum supervised interruptions (supervision is always on; default 10, maximum 100)").option("--max-rounds <n>", "Maximum goal rounds").option("--max-cost <usd>", "Maximum total cost in USD").option("--max-attempts-per-task <n>", "Attempts per Task before opening a decision gate").option("--max-concurrent-tasks <n>", "How many of this goal's tasks may run at once (default 3)").option("--max-steps-per-run <n>", "Optional agent step cap per Task run (for example 500)").option("--operation-lease-timeout-ms <n>", "Reclaim a Task operation after this idle time (minimum: 60000)").option("--conversation", "Link the goal to the current conversation. Use it whenever you create a goal for the user from a chat, not only for /goal: without it the goal is standalone and never shows on this conversation's goal tray. This agent supervises the goal from this conversation; the output carries a turnToken, so submit the first plan with `lh goal plan <id> --token <turnToken> --file <plan.json>`").option("--criterion <text...>", "Acceptance criterion (repeatable)").option("--json [fields]", "Output JSON").action(async (title, options) => {
|
|
509660
510745
|
if (options.maxExperiments && !options.explore) throw new Error("--max-experiments requires --explore");
|
|
509661
510746
|
const operationId = process.env.LOBEHUB_OPERATION_ID;
|
|
509662
510747
|
if (options.conversation && !operationId) throw new Error("--conversation must run inside an agent conversation (LOBEHUB_OPERATION_ID)");
|
|
509663
510748
|
const client = await getTrpcClient();
|
|
509664
510749
|
const buildUrl = await resolveAppUrlBuilder(client);
|
|
509665
510750
|
const goalInput = {
|
|
509666
|
-
config:
|
|
509667
|
-
taskAgentId: options.taskAgent,
|
|
509668
|
-
manager: options.maxManagerTurns ? { maxTurns: Number(options.maxManagerTurns) } : void 0,
|
|
510751
|
+
config: {
|
|
509669
510752
|
exploration: options.explore ? {
|
|
509670
510753
|
instruction: options.explore,
|
|
509671
510754
|
maxExperiments: Number(options.maxExperiments ?? 10)
|
|
509672
510755
|
} : void 0,
|
|
509673
|
-
|
|
509674
|
-
enabled: true,
|
|
509675
|
-
maxIncidents: options.maxSupervisionIncidents ? Number.parseInt(options.maxSupervisionIncidents, 10) : void 0
|
|
509676
|
-
} : void 0,
|
|
510756
|
+
manager: options.maxManagerTurns ? { maxTurns: Number(options.maxManagerTurns) } : void 0,
|
|
509677
510757
|
maxConcurrentTasks: options.maxConcurrentTasks ? Number.parseInt(options.maxConcurrentTasks, 10) : void 0,
|
|
509678
510758
|
recovery: {
|
|
509679
510759
|
maxAttemptsPerTask: options.maxAttemptsPerTask ? Number.parseInt(options.maxAttemptsPerTask, 10) : void 0,
|
|
509680
510760
|
maxStepsPerRun: options.maxStepsPerRun ? Number.parseInt(options.maxStepsPerRun, 10) : void 0,
|
|
509681
510761
|
operationLeaseTimeoutMs: options.operationLeaseTimeoutMs ? Number.parseInt(options.operationLeaseTimeoutMs, 10) : void 0
|
|
509682
|
-
}
|
|
509683
|
-
|
|
510762
|
+
},
|
|
510763
|
+
supervision: {
|
|
510764
|
+
enabled: true,
|
|
510765
|
+
...options.maxSupervisionIncidents ? { maxIncidents: Number.parseInt(options.maxSupervisionIncidents, 10) } : {}
|
|
510766
|
+
},
|
|
510767
|
+
taskAgentId: options.taskAgent
|
|
510768
|
+
},
|
|
509684
510769
|
criteria: options.criterion?.map((criterion) => ({ title: criterion })),
|
|
509685
510770
|
maxRounds: options.maxRounds ? Number.parseInt(options.maxRounds, 10) : void 0,
|
|
509686
510771
|
maxTotalCost: options.maxCost ? Number.parseFloat(options.maxCost) : void 0,
|
|
@@ -513776,7 +514861,7 @@ function object(shape, params) {
|
|
|
513776
514861
|
}
|
|
513777
514862
|
|
|
513778
514863
|
//#endregion
|
|
513779
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
514864
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
513780
514865
|
function isZ4Schema(s) {
|
|
513781
514866
|
return !!s._zod;
|
|
513782
514867
|
}
|
|
@@ -513900,7 +514985,7 @@ function getLiteralValue(schema) {
|
|
|
513900
514985
|
}
|
|
513901
514986
|
|
|
513902
514987
|
//#endregion
|
|
513903
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
514988
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
513904
514989
|
const LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
513905
514990
|
const DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
|
|
513906
514991
|
const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
@@ -515866,7 +516951,7 @@ var UrlElicitationRequiredError = class extends McpError {
|
|
|
515866
516951
|
};
|
|
515867
516952
|
|
|
515868
516953
|
//#endregion
|
|
515869
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
516954
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
|
|
515870
516955
|
/**
|
|
515871
516956
|
* Experimental task interfaces for MCP SDK.
|
|
515872
516957
|
* WARNING: These APIs are experimental and may change without notice.
|
|
@@ -517009,7 +518094,7 @@ const zodToJsonSchema = (schema, options) => {
|
|
|
517009
518094
|
};
|
|
517010
518095
|
|
|
517011
518096
|
//#endregion
|
|
517012
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
518097
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
|
|
517013
518098
|
function mapMiniTarget(t) {
|
|
517014
518099
|
if (!t) return "draft-7";
|
|
517015
518100
|
if (t === "jsonSchema7" || t === "draft-7") return "draft-7";
|
|
@@ -517040,7 +518125,7 @@ function parseWithCompat(schema, data) {
|
|
|
517040
518125
|
}
|
|
517041
518126
|
|
|
517042
518127
|
//#endregion
|
|
517043
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
518128
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
517044
518129
|
/**
|
|
517045
518130
|
* The default request timeout, in miliseconds.
|
|
517046
518131
|
*/
|
|
@@ -518218,7 +519303,7 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
518218
519303
|
}));
|
|
518219
519304
|
|
|
518220
519305
|
//#endregion
|
|
518221
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
519306
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
|
|
518222
519307
|
/**
|
|
518223
519308
|
* AJV-based JSON Schema validator provider
|
|
518224
519309
|
*/
|
|
@@ -518297,7 +519382,7 @@ var AjvJsonSchemaValidator = class {
|
|
|
518297
519382
|
};
|
|
518298
519383
|
|
|
518299
519384
|
//#endregion
|
|
518300
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
519385
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
|
|
518301
519386
|
/**
|
|
518302
519387
|
* Experimental server task features for MCP SDK.
|
|
518303
519388
|
* WARNING: These APIs are experimental and may change without notice.
|
|
@@ -518518,7 +519603,7 @@ var ExperimentalServerTasks = class {
|
|
|
518518
519603
|
};
|
|
518519
519604
|
|
|
518520
519605
|
//#endregion
|
|
518521
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
519606
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
518522
519607
|
/**
|
|
518523
519608
|
* Experimental task capability assertion helpers.
|
|
518524
519609
|
* WARNING: These APIs are experimental and may change without notice.
|
|
@@ -518570,7 +519655,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
|
518570
519655
|
}
|
|
518571
519656
|
|
|
518572
519657
|
//#endregion
|
|
518573
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
519658
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
|
|
518574
519659
|
/**
|
|
518575
519660
|
* An MCP server on top of a pluggable transport.
|
|
518576
519661
|
*
|
|
@@ -518905,7 +519990,7 @@ var Server = class extends Protocol {
|
|
|
518905
519990
|
};
|
|
518906
519991
|
|
|
518907
519992
|
//#endregion
|
|
518908
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
519993
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
|
518909
519994
|
const COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
|
518910
519995
|
/**
|
|
518911
519996
|
* Checks if a schema is completable (has completion metadata).
|
|
@@ -518925,7 +520010,7 @@ var McpZodTypeKind;
|
|
|
518925
520010
|
})(McpZodTypeKind || (McpZodTypeKind = {}));
|
|
518926
520011
|
|
|
518927
520012
|
//#endregion
|
|
518928
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
520013
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
|
|
518929
520014
|
/**
|
|
518930
520015
|
* Tool name validation utilities according to SEP: Specify Format for Tool Names
|
|
518931
520016
|
*
|
|
@@ -518997,7 +520082,7 @@ function validateAndWarnToolName(name) {
|
|
|
518997
520082
|
}
|
|
518998
520083
|
|
|
518999
520084
|
//#endregion
|
|
519000
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
520085
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
|
|
519001
520086
|
/**
|
|
519002
520087
|
* Experimental McpServer task features for MCP SDK.
|
|
519003
520088
|
* WARNING: These APIs are experimental and may change without notice.
|
|
@@ -519029,7 +520114,7 @@ var ExperimentalMcpServerTasks = class {
|
|
|
519029
520114
|
};
|
|
519030
520115
|
|
|
519031
520116
|
//#endregion
|
|
519032
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
520117
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
|
|
519033
520118
|
init_zod$1();
|
|
519034
520119
|
function toolInputElementCount(value, max) {
|
|
519035
520120
|
let count = 0;
|
|
@@ -519683,11 +520768,11 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
519683
520768
|
} };
|
|
519684
520769
|
|
|
519685
520770
|
//#endregion
|
|
519686
|
-
//#region node_modules/.pnpm/@hono+node-server@2.1.3_hono@4.13.
|
|
520771
|
+
//#region node_modules/.pnpm/@hono+node-server@2.1.3_hono@4.13.13/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
|
|
519687
520772
|
const X_ALREADY_SENT = "x-hono-already-sent";
|
|
519688
520773
|
|
|
519689
520774
|
//#endregion
|
|
519690
|
-
//#region node_modules/.pnpm/hono@4.13.
|
|
520775
|
+
//#region node_modules/.pnpm/hono@4.13.13/node_modules/hono/dist/helper/websocket/index.js
|
|
519691
520776
|
/**
|
|
519692
520777
|
* Create a WebSocket adapter/helper
|
|
519693
520778
|
*/
|
|
@@ -519712,7 +520797,7 @@ const defineWebSocketHelper = (handler) => {
|
|
|
519712
520797
|
};
|
|
519713
520798
|
|
|
519714
520799
|
//#endregion
|
|
519715
|
-
//#region node_modules/.pnpm/@hono+node-server@2.1.3_hono@4.13.
|
|
520800
|
+
//#region node_modules/.pnpm/@hono+node-server@2.1.3_hono@4.13.13/node_modules/@hono/node-server/dist/index.mjs
|
|
519716
520801
|
var RequestError = class extends Error {
|
|
519717
520802
|
constructor(message, options) {
|
|
519718
520803
|
super(message, options);
|
|
@@ -520978,7 +522063,7 @@ var require_content_type = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
520978
522063
|
}));
|
|
520979
522064
|
|
|
520980
522065
|
//#endregion
|
|
520981
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
522066
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
|
|
520982
522067
|
var import_content_type = /* @__PURE__ */ __toESM(require_content_type(), 1);
|
|
520983
522068
|
/**
|
|
520984
522069
|
* Extracts the media type (the lowercased `type/subtype` pair, without
|
|
@@ -521025,7 +522110,7 @@ function isJsonContentType(header) {
|
|
|
521025
522110
|
}
|
|
521026
522111
|
|
|
521027
522112
|
//#endregion
|
|
521028
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
522113
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js
|
|
521029
522114
|
/** Default upper bound, in bytes, on a request body read by the HTTP entry points (4 MiB). */
|
|
521030
522115
|
const DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
521031
522116
|
/** The message answered with 413 for a request body over `maxBytes`. */
|
|
@@ -521076,7 +522161,7 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
|
|
|
521076
522161
|
}
|
|
521077
522162
|
|
|
521078
522163
|
//#endregion
|
|
521079
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
522164
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
|
|
521080
522165
|
/** Default interval between SSE keep-alive comment frames. */
|
|
521081
522166
|
const DEFAULT_SSE_KEEP_ALIVE_MS = 15e3;
|
|
521082
522167
|
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
@@ -521089,7 +522174,7 @@ function armSseKeepAlive(intervalMs, onTick) {
|
|
|
521089
522174
|
}
|
|
521090
522175
|
|
|
521091
522176
|
//#endregion
|
|
521092
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
522177
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
521093
522178
|
/**
|
|
521094
522179
|
* Web Standards Streamable HTTP Server Transport
|
|
521095
522180
|
*
|
|
@@ -521757,7 +522842,7 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
521757
522842
|
};
|
|
521758
522843
|
|
|
521759
522844
|
//#endregion
|
|
521760
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.
|
|
522845
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
|
|
521761
522846
|
/**
|
|
521762
522847
|
* Node.js HTTP Streamable HTTP Server Transport
|
|
521763
522848
|
*
|
|
@@ -528305,10 +529390,10 @@ function registerLifecycleCommands(task) {
|
|
|
528305
529390
|
return;
|
|
528306
529391
|
}
|
|
528307
529392
|
const { serverUrl, headers } = await getAuthInfo();
|
|
528308
|
-
await streamAgentEvents(`${serverUrl}/api/agent/stream?operationId=${encodeURIComponent(result.operationId)}`, headers, {
|
|
529393
|
+
if ((await streamAgentEvents(`${serverUrl}/api/agent/stream?operationId=${encodeURIComponent(result.operationId)}`, headers, {
|
|
528309
529394
|
json: options.json,
|
|
528310
529395
|
verbose: options.verbose
|
|
528311
|
-
});
|
|
529396
|
+
}))?.kind === "failed") process.exit(1);
|
|
528312
529397
|
try {
|
|
528313
529398
|
await client.task.heartbeat.mutate({ id });
|
|
528314
529399
|
} catch {}
|
|
@@ -528353,10 +529438,10 @@ function registerLifecycleCommands(task) {
|
|
|
528353
529438
|
return;
|
|
528354
529439
|
}
|
|
528355
529440
|
const { serverUrl, headers } = await getAuthInfo();
|
|
528356
|
-
await streamAgentEvents(`${serverUrl}/api/agent/stream?operationId=${encodeURIComponent(operationId)}`, headers, {
|
|
529441
|
+
if ((await streamAgentEvents(`${serverUrl}/api/agent/stream?operationId=${encodeURIComponent(operationId)}`, headers, {
|
|
528357
529442
|
json: options.json,
|
|
528358
529443
|
verbose: options.verbose
|
|
528359
|
-
});
|
|
529444
|
+
}))?.kind === "failed") process.exit(1);
|
|
528360
529445
|
try {
|
|
528361
529446
|
await client.task.heartbeat.mutate({ id });
|
|
528362
529447
|
} catch {}
|
|
@@ -530847,7 +531932,7 @@ function evidenceTypeForFile(file) {
|
|
|
530847
531932
|
function inlineTextEvidenceForFile(file, type) {
|
|
530848
531933
|
if (!INLINE_TEXT_EVIDENCE_TYPES.has(type)) return void 0;
|
|
530849
531934
|
try {
|
|
530850
|
-
const buffer = readFileSync
|
|
531935
|
+
const buffer = readFileSync(file);
|
|
530851
531936
|
if (buffer.includes(0)) return void 0;
|
|
530852
531937
|
const content = buffer.toString("utf8");
|
|
530853
531938
|
return content.length > 0 && content.length < 5e3 ? content : void 0;
|
|
@@ -531387,7 +532472,7 @@ function interactionCostFromReportDir(dir, result) {
|
|
|
531387
532472
|
if (!existsSync(tracePath)) return void 0;
|
|
531388
532473
|
let raw;
|
|
531389
532474
|
try {
|
|
531390
|
-
raw = readFileSync
|
|
532475
|
+
raw = readFileSync(tracePath, "utf8");
|
|
531391
532476
|
} catch {
|
|
531392
532477
|
log$13.warn(`${GOMS_KLM_TRACE_FILE} could not be read — publishing without interaction cost`);
|
|
531393
532478
|
return;
|
|
@@ -532018,7 +533103,7 @@ async function ingestReportAction(reportDir, options) {
|
|
|
532018
533103
|
}
|
|
532019
533104
|
let result;
|
|
532020
533105
|
try {
|
|
532021
|
-
result = JSON.parse(readFileSync
|
|
533106
|
+
result = JSON.parse(readFileSync(resultPath, "utf8"));
|
|
532022
533107
|
} catch {
|
|
532023
533108
|
log$13.error("result.json is not valid JSON");
|
|
532024
533109
|
process.exit(1);
|
|
@@ -532043,9 +533128,9 @@ async function ingestReportAction(reportDir, options) {
|
|
|
532043
533128
|
});
|
|
532044
533129
|
const summary = result.summary ?? {};
|
|
532045
533130
|
const reportMdPath = path.join(dir, "report.md");
|
|
532046
|
-
const content = existsSync(reportMdPath) ? readFileSync
|
|
533131
|
+
const content = existsSync(reportMdPath) ? readFileSync(reportMdPath, "utf8") : void 0;
|
|
532047
533132
|
const proposalMdPath = path.join(dir, "proposal.md");
|
|
532048
|
-
const proposal = (existsSync(proposalMdPath) ? readFileSync
|
|
533133
|
+
const proposal = (existsSync(proposalMdPath) ? readFileSync(proposalMdPath, "utf8") : typeof result.proposal === "string" ? result.proposal : "").trim();
|
|
532049
533134
|
const scenario = scenarioFromResult(result);
|
|
532050
533135
|
let context;
|
|
532051
533136
|
let pullRequest;
|
|
@@ -532073,6 +533158,7 @@ async function ingestReportAction(reportDir, options) {
|
|
|
532073
533158
|
process.exit(1);
|
|
532074
533159
|
}
|
|
532075
533160
|
let subject = subjectFromResult(result);
|
|
533161
|
+
let foldTaskRunTopic = false;
|
|
532076
533162
|
if (!requestedAcceptanceId && options.subject) {
|
|
532077
533163
|
const ref = parseSubjectRef(options.subject);
|
|
532078
533164
|
if (!ref) {
|
|
@@ -532089,6 +533175,7 @@ async function ingestReportAction(reportDir, options) {
|
|
|
532089
533175
|
} else if (!requestedAcceptanceId && !subject) {
|
|
532090
533176
|
const ref = subjectFromEnv();
|
|
532091
533177
|
if (ref) subject = { ref };
|
|
533178
|
+
foldTaskRunTopic = Boolean(ref);
|
|
532092
533179
|
}
|
|
532093
533180
|
if (!requestedAcceptanceId && !subject) subject = { ref: {
|
|
532094
533181
|
subjectId: randomUUID(),
|
|
@@ -532122,8 +533209,16 @@ async function ingestReportAction(reportDir, options) {
|
|
|
532122
533209
|
requirement,
|
|
532123
533210
|
subjectId: subject.ref.subjectId,
|
|
532124
533211
|
subjectType: subject.ref.subjectType,
|
|
533212
|
+
...foldTaskRunTopic ? { foldTaskRunTopic } : {},
|
|
532125
533213
|
...subject.ref.subjectType === "standalone" && (title || goal) ? { title: title || goal } : {}
|
|
532126
533214
|
});
|
|
533215
|
+
subject = {
|
|
533216
|
+
...subject,
|
|
533217
|
+
ref: {
|
|
533218
|
+
subjectId: acceptance.subjectId,
|
|
533219
|
+
subjectType: acceptance.subjectType
|
|
533220
|
+
}
|
|
533221
|
+
};
|
|
532127
533222
|
bundle = await client.acceptance.getBundle.query({ id: acceptance.id });
|
|
532128
533223
|
}
|
|
532129
533224
|
plan = reuseSourceCriteria(plan, bundle?.checks);
|
|
@@ -532949,20 +534044,12 @@ function registerVerifyCommand(program) {
|
|
|
532949
534044
|
console.log(`${import_picocolors.default.green("✓")} Rubric ${import_picocolors.default.bold(rubricId)} now has ${criterionIds.length} criterion(s)`);
|
|
532950
534045
|
});
|
|
532951
534046
|
const plan = verify.command("plan").description("Per-run check plan lifecycle");
|
|
532952
|
-
plan.command("generate <operationId>").description("Generate a draft check plan for a run").requiredOption("--goal <goal>", "The run's task/instruction the plan must satisfy").option("--rubric <id>", "Mounted rubric id").option("--criteria <ids>", "Ad-hoc criterion ids (comma-separated)").option("--ai", "Let the LLM propose additional criteria").option("--max-ai <n>", "Max AI-proposed criteria").option("--
|
|
532953
|
-
if (options.ai && (!options.model || !options.provider)) {
|
|
532954
|
-
log$13.error("--ai requires --model and --provider");
|
|
532955
|
-
process.exit(1);
|
|
532956
|
-
}
|
|
534047
|
+
plan.command("generate <operationId>").description("Generate a draft check plan for a run").requiredOption("--goal <goal>", "The run's task/instruction the plan must satisfy").option("--rubric <id>", "Mounted rubric id").option("--criteria <ids>", "Ad-hoc criterion ids (comma-separated)").option("--ai", "Let the LLM propose additional criteria").option("--max-ai <n>", "Max AI-proposed criteria").option("--context <text>", "Extra context for the AI prompt").option("--json [fields]", "Output JSON").action(async (operationId, options) => {
|
|
532957
534048
|
const items = await (await getTrpcClient()).verify.generateDraftPlan.mutate({
|
|
532958
534049
|
context: options.context,
|
|
532959
534050
|
enableAiGeneration: options.ai,
|
|
532960
534051
|
goal: options.goal,
|
|
532961
534052
|
maxAiCriteria: options.maxAi ? Number.parseInt(options.maxAi, 10) : void 0,
|
|
532962
|
-
modelConfig: options.model && options.provider ? {
|
|
532963
|
-
model: options.model,
|
|
532964
|
-
provider: options.provider
|
|
532965
|
-
} : void 0,
|
|
532966
534053
|
operationId,
|
|
532967
534054
|
verifyCriteriaIds: options.criteria?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
532968
534055
|
verifyRubricId: options.rubric ?? null
|