lody 0.41.7-next.1 → 0.41.8-next.1

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.
Files changed (2) hide show
  1. package/dist/index.js +520 -116
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -40324,7 +40324,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
40324
40324
  }
40325
40325
  }
40326
40326
  const name = "lody";
40327
- const version$3 = "0.41.7-next.1";
40327
+ const version$3 = "0.41.8-next.1";
40328
40328
  const description = "Lody Agent CLI tool for managing remote command execution";
40329
40329
  const type = "module";
40330
40330
  const main$3 = "dist/index.js";
@@ -70344,6 +70344,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
70344
70344
  componentsGeneric();
70345
70345
  const getAcpCapabilityCacheKey = (cliType, agentType) => `${cliType}:${agentType}`;
70346
70346
  const isBuiltinAgentType = (agentType) => agentType === "claude" || agentType === "codex";
70347
+ const selectFullAccessModeValue = (cliType, agentType, option2) => {
70348
+ if (option2.category !== "mode") {
70349
+ return void 0;
70350
+ }
70351
+ if (cliType === "builtin" && agentType === "claude") {
70352
+ return option2.options.find((candidate) => candidate.value === "bypassPermissions")?.value;
70353
+ }
70354
+ if (cliType === "builtin" && agentType === "codex") {
70355
+ return option2.options.find((candidate) => candidate.value === "full-access")?.value;
70356
+ }
70357
+ return void 0;
70358
+ };
70359
+ function computeFullAccessConfigDefaults(cliType, agentType, configOptions) {
70360
+ const defaults2 = {};
70361
+ for (const option2 of configOptions) {
70362
+ const fullAccessModeValue = selectFullAccessModeValue(cliType, agentType, option2);
70363
+ if (fullAccessModeValue) {
70364
+ defaults2[option2.id] = fullAccessModeValue;
70365
+ }
70366
+ }
70367
+ return defaults2;
70368
+ }
70347
70369
  function computeTitleGenerationDefaults(cliType, agentType, configOptions) {
70348
70370
  const defaults2 = {};
70349
70371
  for (const opt of configOptions) {
@@ -70352,8 +70374,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
70352
70374
  const haiku = opt.options.find((o) => /haiku/i.test(o.value) || /haiku/i.test(o.name));
70353
70375
  defaults2[opt.id] = haiku?.value ?? opt.currentValue;
70354
70376
  } else if (opt.category === "mode") {
70355
- const fullAccess = opt.options.find((o) => o.value === "bypassPermissions");
70356
- defaults2[opt.id] = fullAccess?.value ?? opt.currentValue;
70377
+ defaults2[opt.id] = selectFullAccessModeValue(cliType, agentType, opt) ?? opt.currentValue;
70357
70378
  } else {
70358
70379
  defaults2[opt.id] = opt.currentValue;
70359
70380
  }
@@ -70362,8 +70383,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
70362
70383
  const codexModel = opt.options.find((o) => /gpt-5\.2-codex/i.test(o.value));
70363
70384
  defaults2[opt.id] = codexModel?.value ?? opt.currentValue;
70364
70385
  } else if (opt.category === "mode") {
70365
- const fullAccess = opt.options.find((o) => o.value === "full-access");
70366
- defaults2[opt.id] = fullAccess?.value ?? opt.currentValue;
70386
+ defaults2[opt.id] = selectFullAccessModeValue(cliType, agentType, opt) ?? opt.currentValue;
70367
70387
  } else if (opt.id === "reasoning_effort" || opt.category === "thought_level") {
70368
70388
  const low = opt.options.find((o) => o.value === "low");
70369
70389
  defaults2[opt.id] = low?.value ?? opt.currentValue;
@@ -74309,6 +74329,9 @@ Task description:
74309
74329
  };
74310
74330
  const sessionHistorySchema = schema.LoroMap({
74311
74331
  id: schema.String(),
74332
+ userTurnId: schema.String({
74333
+ required: false
74334
+ }),
74312
74335
  items: schema.LoroList(historyMessageItemSchema, void 0, {
74313
74336
  required: false
74314
74337
  }),
@@ -100983,7 +101006,7 @@ path=/${options.repoFullName}.git
100983
101006
  }
100984
101007
  function loadACPIdleTimerConfig() {
100985
101008
  return {
100986
- idleTimeoutMs: readEnvNumber$1("LODY_ACP_IDLE_TIMEOUT_MS", 3 * 60 * 60 * 1e3),
101009
+ idleTimeoutMs: readEnvNumber$1("LODY_ACP_IDLE_TIMEOUT_MS", 30 * 60 * 1e3),
100987
101010
  enabled: process.env.LODY_ACP_IDLE_TIMER_ENABLED !== "false"
100988
101011
  };
100989
101012
  }
@@ -101869,7 +101892,7 @@ postId\u662F${normalizedFeedbackPostId}\uFF0C\u4F60\u9700\u8981\u5728\u5408\u900
101869
101892
  if (await abortIfCurrentTurnCancelled()) {
101870
101893
  return;
101871
101894
  }
101872
- await this.deps.createAssistantEntryForTurn(sessionId, sessionDoc, conversationTurnId, session.agentClient?.currentModel);
101895
+ await this.deps.createAssistantEntryForTurn(sessionId, sessionDoc, conversationTurnId, session.agentClient?.currentModel, userTurnId);
101873
101896
  if (await abortIfCurrentTurnCancelled()) {
101874
101897
  return;
101875
101898
  }
@@ -102052,7 +102075,7 @@ postId\u662F${normalizedFeedbackPostId}\uFF0C\u4F60\u9700\u8981\u5728\u5408\u900
102052
102075
  if (await abortIfCurrentTurnCancelled()) {
102053
102076
  return;
102054
102077
  }
102055
- await this.deps.createAssistantEntryForTurn(sessionId, sessionDoc, conversationTurnId, session.agentClient?.currentModel);
102078
+ await this.deps.createAssistantEntryForTurn(sessionId, sessionDoc, conversationTurnId, session.agentClient?.currentModel, userTurnId);
102056
102079
  const normalizedInputBlocks = normalizeSessionInputBlocks(agentConfig.inputBlocks, agentConfig.prompt);
102057
102080
  const imageOnlyInputBlocks = normalizedInputBlocks.filter((block) => block.type === "image");
102058
102081
  const createPromptText = buildPrompt(agentConfig.prompt, project, agentConfig.issuePRMentions, fromFeedbackPostId);
@@ -102672,7 +102695,7 @@ postId\u662F${normalizedFeedbackPostId}\uFF0C\u4F60\u9700\u8981\u5728\u5408\u900
102672
102695
  clearActiveTurnId: (sessionId, turnId) => this.clearActiveTurnIdIfMatches(sessionId, turnId),
102673
102696
  buildAcpPromptBlocks: async (args2) => await this.buildAcpPromptBlocks(args2),
102674
102697
  applyAcpModeAndModel: async (session, config22) => await this.applyAcpModeAndModel(session, config22),
102675
- createAssistantEntryForTurn: async (sessionId, sessionDoc, turnId, modelInfo) => await this.createAssistantEntryForTurn(sessionId, sessionDoc, turnId, modelInfo),
102698
+ createAssistantEntryForTurn: async (sessionId, sessionDoc, turnId, modelInfo, userTurnId) => await this.createAssistantEntryForTurn(sessionId, sessionDoc, turnId, modelInfo, userTurnId),
102676
102699
  finalizeTurn: async (ctx) => await this.finalizeTurn(ctx),
102677
102700
  handleTurnError: async (sessionId, sessionDoc, error2) => await this.handleTurnError(sessionId, sessionDoc, error2),
102678
102701
  recordChatFailure: async (sessionDoc, reason, message) => await this.recordChatFailure(sessionDoc, reason, message),
@@ -102952,12 +102975,13 @@ postId\u662F${normalizedFeedbackPostId}\uFF0C\u4F60\u9700\u8981\u5728\u5408\u900
102952
102975
  this.logger.warn(`[${sessionId}] Failed to record code session diffs: ${formatErrorMessage(error2)}`);
102953
102976
  }
102954
102977
  }
102955
- async createAssistantEntryForTurn(sessionId, sessionDoc, turnId, modelInfo) {
102978
+ async createAssistantEntryForTurn(sessionId, sessionDoc, turnId, modelInfo, userTurnId) {
102956
102979
  this.logger.debug(`[${sessionId}] Creating assistant entry for turn ${turnId}`);
102957
102980
  await sessionDoc.updateHistory((history) => {
102958
102981
  history.push({
102959
102982
  id: turnId,
102960
102983
  role: "assistant",
102984
+ userTurnId,
102961
102985
  items: [],
102962
102986
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
102963
102987
  userId: void 0,
@@ -106502,6 +106526,14 @@ const getSessionId = () => {
106502
106526
  return process.env.LODY_SESSION_ID || extractSessionIdFromPath();
106503
106527
  };
106504
106528
 
106529
+ const getAssociationSessionId = () => {
106530
+ const workspaceSessionId = String(process.env.LODY_WORKSPACE_SESSION_ID || '').trim();
106531
+ if (workspaceSessionId) {
106532
+ return workspaceSessionId;
106533
+ }
106534
+ return getSessionId();
106535
+ };
106536
+
106505
106537
  const parseRepoFromUrl = (raw) => {
106506
106538
  const value = String(raw || '').trim();
106507
106539
  if (!value) return null;
@@ -106740,7 +106772,7 @@ const postPullRequestAssociation = async (payload) => {
106740
106772
  const baseUrl = process.env.LODY_AUTH_URL;
106741
106773
  const cliToken = process.env.LODY_CLI_TOKEN;
106742
106774
  const workspaceId = process.env.LODY_WORKSPACE_ID;
106743
- const sessionId = getSessionId();
106775
+ const sessionId = getAssociationSessionId();
106744
106776
  if (!sessionId) return;
106745
106777
  if (!baseUrl || !cliToken || !workspaceId) {
106746
106778
  console.error('[lody] PR association skipped: missing auth configuration.');
@@ -112131,6 +112163,7 @@ export PATH="${ghShimBinDir}:$PATH"
112131
112163
  }
112132
112164
  buildShellEnv(extraEnv) {
112133
112165
  const configEnv = this.config.env ?? {};
112166
+ const workspaceSessionId = this.config.parentSessionId ?? this.sessionId;
112134
112167
  return {
112135
112168
  ...process.env,
112136
112169
  ...configEnv,
@@ -112139,7 +112172,8 @@ export PATH="${ghShimBinDir}:$PATH"
112139
112172
  TERM: "xterm-256color",
112140
112173
  PS1: "",
112141
112174
  PROMPT_COMMAND: "",
112142
- LODY_SESSION_ID: this.sessionId
112175
+ LODY_SESSION_ID: this.sessionId,
112176
+ LODY_WORKSPACE_SESSION_ID: workspaceSessionId
112143
112177
  };
112144
112178
  }
112145
112179
  async createAgent(callbacks) {
@@ -141268,6 +141302,12 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
141268
141302
  slug: string$1().nullable(),
141269
141303
  role: string$1()
141270
141304
  });
141305
+ const WorkspaceGitHubRepositorySchema = object({
141306
+ id: number$1(),
141307
+ name: string$1(),
141308
+ fullName: string$1(),
141309
+ private: boolean()
141310
+ });
141271
141311
  const WorkspaceListResultSchema = discriminatedUnion("valid", [
141272
141312
  object({
141273
141313
  valid: literal(false),
@@ -141284,6 +141324,16 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
141284
141324
  success: literal(true),
141285
141325
  existing: boolean()
141286
141326
  });
141327
+ const WorkspaceGitHubRepositoryListResultSchema = discriminatedUnion("valid", [
141328
+ object({
141329
+ valid: literal(false),
141330
+ repositories: array$1(WorkspaceGitHubRepositorySchema)
141331
+ }),
141332
+ object({
141333
+ valid: literal(true),
141334
+ repositories: array$1(WorkspaceGitHubRepositorySchema)
141335
+ })
141336
+ ]);
141287
141337
  function createAuthConvexClient() {
141288
141338
  if (!LODY_AUTH_URL) {
141289
141339
  throw new Error("LODY_AUTH_URL is not defined.");
@@ -141301,6 +141351,18 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
141301
141351
  }
141302
141352
  return parsed.workspaces;
141303
141353
  }
141354
+ async function listWorkspaceGitHubRepositoriesForCliToken(input2) {
141355
+ const client = createAuthConvexClient();
141356
+ const raw = await client.query(api.github.listWorkspaceRepositoriesForCliToken, {
141357
+ cliToken: input2.token,
141358
+ workspaceId: input2.workspaceId
141359
+ });
141360
+ const parsed = WorkspaceGitHubRepositoryListResultSchema.parse(raw);
141361
+ if (!parsed.valid) {
141362
+ throw new Error("CLI token is invalid or expired. Run `lody login` again.");
141363
+ }
141364
+ return parsed.repositories;
141365
+ }
141304
141366
  async function registerSessionOwnerForCliToken(input2) {
141305
141367
  const client = createAuthConvexClient();
141306
141368
  const raw = await client.mutation(api.sessions.registerSessionOwnerFromCliToken, {
@@ -144504,6 +144566,171 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
144504
144566
  });
144505
144567
  });
144506
144568
  const agentConfigCommand = new Command("agent-config").description("Manage agent configs").addCommand(agentConfigListCommand).addCommand(agentConfigShowCommand).addCommand(agentConfigRefreshCapabilitiesCommand).addCommand(agentConfigCreateCommand).addCommand(agentConfigUpdateCommand).addCommand(agentConfigDeleteCommand);
144569
+ class SessionTurnWaitError extends Error {
144570
+ constructor(code, sessionId, userTurnId, message) {
144571
+ super(message);
144572
+ this.code = code;
144573
+ this.sessionId = sessionId;
144574
+ this.userTurnId = userTurnId;
144575
+ this.name = "SessionTurnWaitError";
144576
+ }
144577
+ }
144578
+ const normalizeMessageItems = (items) => {
144579
+ return Array.isArray(items) ? items : [];
144580
+ };
144581
+ const findUserTurnIndex = (history, userTurnId) => {
144582
+ return history.findIndex((entry2) => entry2?.id === userTurnId && entry2.role === "user");
144583
+ };
144584
+ function findAssistantEntryForUserTurn(history, userTurnId) {
144585
+ const userTurnIndex = findUserTurnIndex(history, userTurnId);
144586
+ if (userTurnIndex < 0) {
144587
+ return void 0;
144588
+ }
144589
+ for (let index2 = userTurnIndex + 1; index2 < history.length; index2 += 1) {
144590
+ const entry2 = history[index2];
144591
+ if (entry2?.role === "assistant" && entry2.userTurnId === userTurnId) {
144592
+ return entry2;
144593
+ }
144594
+ }
144595
+ return void 0;
144596
+ }
144597
+ const findUserTurn = (history, userTurnId) => {
144598
+ const userTurnIndex = findUserTurnIndex(history, userTurnId);
144599
+ return userTurnIndex >= 0 ? history[userTurnIndex] : void 0;
144600
+ };
144601
+ const findChatFailureMessage = (history, userTurnId) => {
144602
+ const userTurnIndex = findUserTurnIndex(history, userTurnId);
144603
+ if (userTurnIndex < 0) {
144604
+ return void 0;
144605
+ }
144606
+ for (let index2 = history.length - 1; index2 > userTurnIndex; index2 -= 1) {
144607
+ const entry2 = history[index2];
144608
+ if (entry2?.role !== "system" || !Array.isArray(entry2.items)) {
144609
+ continue;
144610
+ }
144611
+ for (const item of entry2.items) {
144612
+ const noticeMeta = item.type === "system_notice" ? item.meta : void 0;
144613
+ if (item.type === "system_notice" && item.name === "chat_failed" && noticeMeta && typeof noticeMeta === "object" && "message" in noticeMeta && typeof noticeMeta.message === "string" && noticeMeta.message.trim()) {
144614
+ return noticeMeta.message.trim();
144615
+ }
144616
+ }
144617
+ }
144618
+ return void 0;
144619
+ };
144620
+ function calculateTurnDurationMs(entry2) {
144621
+ const startedAt = Date.parse(entry2.timestamp);
144622
+ if (!Number.isFinite(startedAt) || typeof entry2.endedAt !== "number") {
144623
+ return 0;
144624
+ }
144625
+ return Math.max(0, entry2.endedAt - startedAt);
144626
+ }
144627
+ async function waitForTurnCompletion(options) {
144628
+ const mirror = options.sessionDoc.mirror;
144629
+ if (!mirror) {
144630
+ throw new Error("SessionDocument not initialized");
144631
+ }
144632
+ return await new Promise((resolve2, reject) => {
144633
+ let settled = false;
144634
+ let lastAssistantTurnId;
144635
+ let lastSerializedItems = [];
144636
+ let unsubscribe2 = () => {
144637
+ };
144638
+ let timeoutId;
144639
+ const cleanup = () => {
144640
+ unsubscribe2();
144641
+ if (timeoutId) {
144642
+ clearTimeout(timeoutId);
144643
+ }
144644
+ options.signal?.removeEventListener("abort", handleAbort);
144645
+ };
144646
+ const settle = (handler) => {
144647
+ if (settled) {
144648
+ return;
144649
+ }
144650
+ settled = true;
144651
+ cleanup();
144652
+ handler();
144653
+ };
144654
+ const emit = (event) => {
144655
+ options.onEvent?.(event);
144656
+ };
144657
+ const rejectWith = (error2) => {
144658
+ settle(() => reject(error2));
144659
+ };
144660
+ const inspect2 = (next) => {
144661
+ if (settled) {
144662
+ return;
144663
+ }
144664
+ const history = Array.isArray(next.history) ? next.history : [];
144665
+ const userTurn = findUserTurn(history, options.userTurnId);
144666
+ if (userTurn?.status === "failed") {
144667
+ rejectWith(new SessionTurnWaitError("failed", options.sessionDoc.sessionId, options.userTurnId, findChatFailureMessage(history, options.userTurnId) ?? "Session turn failed."));
144668
+ return;
144669
+ }
144670
+ if (userTurn?.status === "canceled") {
144671
+ rejectWith(new SessionTurnWaitError("canceled", options.sessionDoc.sessionId, options.userTurnId, "Session turn was canceled."));
144672
+ return;
144673
+ }
144674
+ const assistantEntry = findAssistantEntryForUserTurn(history, options.userTurnId);
144675
+ if (assistantEntry) {
144676
+ const items = normalizeMessageItems(assistantEntry.items);
144677
+ if (assistantEntry.id !== lastAssistantTurnId) {
144678
+ lastAssistantTurnId = assistantEntry.id;
144679
+ lastSerializedItems = [];
144680
+ }
144681
+ if (options.outputMode === "jsonl") {
144682
+ const nextSerializedItems = items.map((item) => JSON.stringify(item));
144683
+ for (let index2 = 0; index2 < nextSerializedItems.length; index2 += 1) {
144684
+ if (nextSerializedItems[index2] !== lastSerializedItems[index2]) {
144685
+ emit({
144686
+ type: "update",
144687
+ sessionId: options.sessionDoc.sessionId,
144688
+ turnId: assistantEntry.id,
144689
+ content: items[index2]
144690
+ });
144691
+ }
144692
+ }
144693
+ lastSerializedItems = nextSerializedItems;
144694
+ }
144695
+ if (userTurn?.status === "handled" && (assistantEntry.finished === true || typeof assistantEntry.endedAt === "number")) {
144696
+ const completedTurn = {
144697
+ sessionId: options.sessionDoc.sessionId,
144698
+ userTurnId: options.userTurnId,
144699
+ turnId: assistantEntry.id,
144700
+ content: items,
144701
+ durationMs: calculateTurnDurationMs(assistantEntry),
144702
+ entry: assistantEntry
144703
+ };
144704
+ if (options.outputMode === "jsonl") {
144705
+ emit({
144706
+ type: "done",
144707
+ sessionId: options.sessionDoc.sessionId,
144708
+ turnId: assistantEntry.id,
144709
+ durationMs: completedTurn.durationMs
144710
+ });
144711
+ }
144712
+ settle(() => resolve2(completedTurn));
144713
+ return;
144714
+ }
144715
+ }
144716
+ };
144717
+ const handleAbort = () => {
144718
+ rejectWith(new Error("Turn completion wait aborted."));
144719
+ };
144720
+ unsubscribe2 = mirror.subscribe((next) => {
144721
+ inspect2(next);
144722
+ });
144723
+ options.signal?.addEventListener("abort", handleAbort, {
144724
+ once: true
144725
+ });
144726
+ if (options.timeoutMs > 0) {
144727
+ timeoutId = setTimeout(() => {
144728
+ rejectWith(new SessionTurnWaitError("timeout", options.sessionDoc.sessionId, options.userTurnId, `Timed out waiting for session turn completion after ${Math.ceil(options.timeoutMs / 1e3)}s.`));
144729
+ }, options.timeoutMs);
144730
+ }
144731
+ inspect2(mirror.getState());
144732
+ });
144733
+ }
144507
144734
  function resolvePromptCandidate(input2) {
144508
144735
  return normalizeCliValue(input2.prompt) ?? normalizeCliValue(input2.promptFileContent) ?? normalizeCliValue(input2.positionalPrompt) ?? normalizeCliValue(input2.stdinText);
144509
144736
  }
@@ -145005,9 +145232,70 @@ ${entry2.text}`).join("\n\n");
145005
145232
  agentType: args2.agentType,
145006
145233
  modeId: args2.modeId,
145007
145234
  modelId: args2.modelId,
145235
+ configOptionValues: args2.configOptionValues && Object.keys(args2.configOptionValues).length > 0 ? args2.configOptionValues : void 0,
145008
145236
  resume: args2.resume
145009
145237
  };
145010
145238
  }
145239
+ function resolveBuiltinFullAccessModeId(cliType, agentType) {
145240
+ if (cliType !== "builtin") {
145241
+ return void 0;
145242
+ }
145243
+ if (agentType === "claude") {
145244
+ return "bypassPermissions";
145245
+ }
145246
+ if (agentType === "codex") {
145247
+ return "full-access";
145248
+ }
145249
+ return void 0;
145250
+ }
145251
+ function resolveStructuredOutputTimeoutMs(timeoutSeconds) {
145252
+ return (timeoutSeconds ?? 600) * 1e3;
145253
+ }
145254
+ async function resolveTurnDispatchConfig(args2) {
145255
+ const modeId = normalizeCliValue(args2.mode);
145256
+ const modelId = normalizeCliValue(args2.model);
145257
+ if (args2.outputMode === "human" || modeId) {
145258
+ return {
145259
+ modeId: modeId ?? void 0,
145260
+ modelId: modelId ?? void 0
145261
+ };
145262
+ }
145263
+ const capabilities = await args2.manager.getAcpCapabilities(args2.machineId, args2.cliType, args2.agentType);
145264
+ const configOptionValues = capabilities?.configOptions ? computeFullAccessConfigDefaults(args2.cliType, args2.agentType, capabilities.configOptions) : {};
145265
+ if (Object.keys(configOptionValues).length > 0) {
145266
+ return {
145267
+ modelId: modelId ?? void 0,
145268
+ configOptionValues
145269
+ };
145270
+ }
145271
+ return {
145272
+ modeId: resolveBuiltinFullAccessModeId(args2.cliType, args2.agentType),
145273
+ modelId: modelId ?? void 0
145274
+ };
145275
+ }
145276
+ function buildStructuredWaitError(outputMode, sessionId, userTurnId, error2) {
145277
+ const message = formatErrorMessage(error2);
145278
+ if (outputMode === "json") {
145279
+ return Object.assign(new Error(message), {
145280
+ structuredOutputPayload: {
145281
+ ok: false,
145282
+ sessionId,
145283
+ userTurnId,
145284
+ error: message
145285
+ }
145286
+ });
145287
+ }
145288
+ const code = error2 instanceof SessionTurnWaitError ? error2.code : "failed";
145289
+ return Object.assign(new Error(message), {
145290
+ structuredOutputPayload: {
145291
+ type: "error",
145292
+ sessionId,
145293
+ userTurnId,
145294
+ code,
145295
+ error: message
145296
+ }
145297
+ });
145298
+ }
145011
145299
  async function removeHistoryEntryById(sessionDoc, historyId) {
145012
145300
  await sessionDoc.updateHistory((history) => history.filter((entry2) => entry2.id !== historyId));
145013
145301
  }
@@ -145025,38 +145313,42 @@ ${entry2.text}`).join("\n\n");
145025
145313
  logger2.warn(`Failed to update session activity timestamps for ${sessionId}: ${formatErrorMessage(error2)}`);
145026
145314
  }
145027
145315
  }
145028
- const dispatchLocalSessionControl = dispatchLocalControl;
145029
- function extractCreateResponse(responses) {
145030
- const target = responses.find((response) => response.type === "session/create_response");
145316
+ function extractCancelResponse(responses) {
145317
+ const target = responses.find((response) => response.type === "session/cancel_response");
145031
145318
  if (!target) {
145032
- throw new Error("Missing session/create_response from local CLI daemon.");
145319
+ throw new Error("Missing session/cancel_response from local CLI daemon.");
145033
145320
  }
145034
- return SessionCreateResponseSchema.parse(target);
145321
+ return SessionCancelResponseSchema.parse(target);
145035
145322
  }
145036
- function extractChatResponse(responses) {
145037
- const target = responses.find((response) => response.type === "session/chat_response");
145323
+ function extractMachineStatusResponse(responses) {
145324
+ const target = responses.find((response) => response.type === "machine/status_response");
145038
145325
  if (!target) {
145039
- throw new Error("Missing session/chat_response from local CLI daemon.");
145326
+ throw new Error("Missing machine/status_response from local CLI daemon.");
145040
145327
  }
145041
- return SessionChatResponseSchema.parse(target);
145328
+ return MachineStatusResponseSchema.parse(target);
145042
145329
  }
145043
- function extractCancelResponse(responses) {
145044
- const target = responses.find((response) => response.type === "session/cancel_response");
145045
- if (!target) {
145046
- throw new Error("Missing session/cancel_response from local CLI daemon.");
145330
+ async function ensureLocalRuntimeAvailable(machineId, workspaceId) {
145331
+ try {
145332
+ extractMachineStatusResponse(await dispatchLocalControl({
145333
+ type: "machine/status",
145334
+ machineId,
145335
+ workspaceId
145336
+ }));
145337
+ } catch (error2) {
145338
+ throw new Error(`Local session runtime is not available: ${formatErrorMessage(error2)}`, {
145339
+ cause: error2
145340
+ });
145047
145341
  }
145048
- return SessionCancelResponseSchema.parse(target);
145049
145342
  }
145050
145343
  function shouldQueueMachineDelete(session) {
145051
145344
  return normalizeCliValue(session.repoFullName) !== void 0;
145052
145345
  }
145053
- async function createSessionResult(auth, workspace, manager, prompt2, options) {
145346
+ async function createSessionResult(auth, workspace, manager, prompt2, options, agentConfig, dispatchConfig, structuredOutput) {
145054
145347
  const normalizedRepo = normalizeCliValue(options.repo);
145055
145348
  const normalizedLocalProject = normalizeCliValue(options.localProject);
145056
145349
  if (normalizedRepo && normalizedLocalProject) {
145057
145350
  throw new Error("Pass either --repo or --local-project, not both.");
145058
145351
  }
145059
- const agentConfig = await resolveAgentConfigOrThrow(manager, options.agentConfig);
145060
145352
  const requestedBranch = normalizeCliValue(options.branch);
145061
145353
  const branch = resolveBaseBranchPreference({
145062
145354
  preferredBranch: requestedBranch,
@@ -145108,18 +145400,29 @@ ${entry2.text}`).join("\n\n");
145108
145400
  env: mergedEnv
145109
145401
  } : {}
145110
145402
  });
145111
- let response;
145403
+ let completionAbortController;
145404
+ let completionPromise;
145112
145405
  try {
145113
- const modeId = normalizeCliValue(options.mode);
145114
- const modelId = normalizeCliValue(options.model);
145406
+ const modeId = dispatchConfig.modeId;
145407
+ const modelId = dispatchConfig.modelId;
145115
145408
  const sessionCreatePrompt = buildAgentPrompt(prompt2, agentConfig.prompt ?? "");
145116
145409
  const userTurnId = await appendUserPromptHistory(sessionDoc, prompt2, auth.userId, buildCliHistoryInputConfig({
145117
145410
  prompt: sessionCreatePrompt,
145118
145411
  cliType: agentConfig.cliType,
145119
145412
  agentType: agentConfig.agentType,
145120
145413
  modeId: modeId ?? void 0,
145121
- modelId: modelId ?? void 0
145414
+ modelId: modelId ?? void 0,
145415
+ configOptionValues: dispatchConfig.configOptionValues
145122
145416
  }));
145417
+ completionAbortController = structuredOutput ? new AbortController() : void 0;
145418
+ completionPromise = structuredOutput ? waitForTurnCompletion({
145419
+ sessionDoc,
145420
+ userTurnId,
145421
+ outputMode: structuredOutput.outputMode,
145422
+ timeoutMs: structuredOutput.timeoutMs,
145423
+ signal: completionAbortController?.signal,
145424
+ onEvent: structuredOutput.onEvent
145425
+ }) : void 0;
145123
145426
  if (repoFullName) {
145124
145427
  await registerSessionOwnerForCliToken({
145125
145428
  token: auth.token,
@@ -145129,44 +145432,23 @@ ${entry2.text}`).join("\n\n");
145129
145432
  repoFullName
145130
145433
  });
145131
145434
  }
145132
- response = extractCreateResponse(await dispatchLocalSessionControl({
145133
- type: "session/create",
145435
+ await updateSessionActivityTimestampsBestEffort(manager, sessionId);
145436
+ await manager.repo.upsertDocMeta(sessionRoomId, {
145437
+ status: SessionStatusFactory.idle()
145438
+ });
145439
+ return {
145134
145440
  sessionId,
145135
145441
  machineId: auth.machineId,
145136
145442
  workspaceId: workspace.id,
145137
- ...project ? {
145138
- project
145139
- } : {},
145140
- acpSessionConfig: {
145141
- prompt: sessionCreatePrompt,
145142
- cliType: agentConfig.cliType,
145143
- agentType: agentConfig.agentType,
145144
- ...modeId ? {
145145
- modeId
145146
- } : {},
145147
- ...modelId ? {
145148
- modelId
145149
- } : {}
145150
- },
145151
- ...Object.keys(mergedEnv).length > 0 ? {
145152
- env: mergedEnv
145153
- } : {},
145154
145443
  userTurnId,
145155
- userId: auth.userId,
145156
- userName: auth.userName,
145157
- userEmail: auth.userEmail
145158
- }));
145444
+ completionPromise
145445
+ };
145159
145446
  } catch (error2) {
145447
+ completionAbortController?.abort();
145448
+ await completionPromise?.catch(() => void 0);
145160
145449
  await rollbackPendingSessionCreate(manager, sessionId);
145161
145450
  throw error2;
145162
145451
  }
145163
- await updateSessionActivityTimestampsBestEffort(manager, sessionId);
145164
- return {
145165
- sessionId,
145166
- machineId: auth.machineId,
145167
- workspaceId: workspace.id,
145168
- response
145169
- };
145170
145452
  }
145171
145453
  async function buildSessionShowResult(workspace, manager, sessionId) {
145172
145454
  const session = await resolveSessionMetaOrThrow(manager, sessionId);
@@ -145238,11 +145520,15 @@ ${entry2.text}`).join("\n\n");
145238
145520
  await exitSessionCommand(0);
145239
145521
  } catch (error2) {
145240
145522
  const message = formatErrorMessage(error2);
145523
+ const structuredOutputPayload = error2 && typeof error2 === "object" && "structuredOutputPayload" in error2 ? error2.structuredOutputPayload : void 0;
145241
145524
  if (options.json || options.jsonl) {
145242
- printJson({
145525
+ printJson(structuredOutputPayload ?? (options.jsonl ? {
145526
+ type: "error",
145527
+ error: message
145528
+ } : {
145243
145529
  ok: false,
145244
145530
  error: message
145245
- });
145531
+ }));
145246
145532
  } else {
145247
145533
  getLogger("session").error(message);
145248
145534
  }
@@ -145280,29 +145566,70 @@ ${entry2.text}`).join("\n\n");
145280
145566
  process.exit(code);
145281
145567
  }
145282
145568
  }
145283
- const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--branch <name>", "Project branch to use (defaults to main)").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Extra environment variable in KEY=VALUE form; repeatable", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
145569
+ const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--branch <name>", "Project branch to use (defaults to main)").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Extra environment variable in KEY=VALUE form; repeatable", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
145284
145570
  await runSessionCommand(options, async () => {
145571
+ const outputMode = resolveStructuredOutputMode(options);
145285
145572
  const auth = getAuthContextOrThrow();
145286
145573
  const workspace = await resolveWorkspaceOrThrow(auth, options.workspace);
145287
145574
  const prompt2 = await readPromptText(options, promptArg);
145575
+ await ensureLocalRuntimeAvailable(auth.machineId, workspace.id);
145288
145576
  await withWorkspaceManager(auth, workspace, async (manager) => {
145289
- const result = await createSessionResult(auth, workspace, manager, prompt2, options);
145290
- if (options.json) {
145291
- printJson({
145292
- ok: true,
145293
- sessionId: result.sessionId,
145294
- workspaceId: result.workspaceId,
145295
- machineId: result.machineId,
145296
- response: result.response
145297
- });
145577
+ const agentConfig = await resolveAgentConfigOrThrow(manager, options.agentConfig);
145578
+ const dispatchConfig = await resolveTurnDispatchConfig({
145579
+ manager,
145580
+ machineId: auth.machineId,
145581
+ cliType: agentConfig.cliType,
145582
+ agentType: agentConfig.agentType,
145583
+ mode: options.mode,
145584
+ model: options.model,
145585
+ outputMode
145586
+ });
145587
+ const result = await createSessionResult(auth, workspace, manager, prompt2, options, agentConfig, dispatchConfig, outputMode === "human" ? void 0 : {
145588
+ outputMode,
145589
+ timeoutMs: resolveStructuredOutputTimeoutMs(options.timeout),
145590
+ onEvent: outputMode === "jsonl" ? (event) => printJson(event) : void 0
145591
+ });
145592
+ if (outputMode === "json") {
145593
+ try {
145594
+ const completionPromise = result.completionPromise;
145595
+ if (!completionPromise) {
145596
+ throw new Error("Missing completion promise for structured session create output.");
145597
+ }
145598
+ const completedTurn = await completionPromise;
145599
+ printJson({
145600
+ ok: true,
145601
+ sessionId: result.sessionId,
145602
+ workspaceId: result.workspaceId,
145603
+ machineId: result.machineId,
145604
+ userTurnId: result.userTurnId,
145605
+ turnId: completedTurn.turnId,
145606
+ content: completedTurn.content,
145607
+ durationMs: completedTurn.durationMs
145608
+ });
145609
+ } catch (error2) {
145610
+ throw buildStructuredWaitError(outputMode, result.sessionId, result.userTurnId, error2);
145611
+ }
145612
+ return;
145613
+ }
145614
+ if (outputMode === "jsonl") {
145615
+ try {
145616
+ const completionPromise = result.completionPromise;
145617
+ if (!completionPromise) {
145618
+ throw new Error("Missing completion promise for structured session create output.");
145619
+ }
145620
+ await completionPromise;
145621
+ } catch (error2) {
145622
+ throw buildStructuredWaitError(outputMode, result.sessionId, result.userTurnId, error2);
145623
+ }
145298
145624
  return;
145299
145625
  }
145300
145626
  console.log(result.sessionId);
145301
145627
  });
145302
145628
  });
145303
145629
  });
145304
- const sessionChatCommand = new Command("chat").description("Send a new user prompt to an existing session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").argument("[prompt]", "Prompt text").action(async (sessionIdArg, promptArg, options) => {
145630
+ const sessionChatCommand = new Command("chat").description("Send a new user prompt to an existing session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").argument("[prompt]", "Prompt text").action(async (sessionIdArg, promptArg, options) => {
145305
145631
  await runSessionCommand(options, async () => {
145632
+ const outputMode = resolveStructuredOutputMode(options);
145306
145633
  const auth = getAuthContextOrThrow();
145307
145634
  const stdinState = shouldReadStdinForChatArgResolution({
145308
145635
  sessionIdArg,
@@ -145332,58 +145659,71 @@ ${entry2.text}`).join("\n\n");
145332
145659
  throw new Error(`Session ${sessionId} is archived. Restore it before chatting.`);
145333
145660
  }
145334
145661
  ensureLocalMachineSession(session, auth);
145662
+ await ensureLocalRuntimeAvailable(auth.machineId, workspace.id);
145335
145663
  const sessionDoc = await manager.getOrCreateSessionDoc(sessionId);
145336
- const modeId = normalizeCliValue(options.mode);
145337
- const modelId = normalizeCliValue(options.model);
145664
+ const dispatchConfig = await resolveTurnDispatchConfig({
145665
+ manager,
145666
+ machineId: auth.machineId,
145667
+ cliType: session.cliType,
145668
+ agentType: session.agentType,
145669
+ mode: options.mode,
145670
+ model: options.model,
145671
+ outputMode
145672
+ });
145338
145673
  const userTurnId = await appendUserPromptHistory(sessionDoc, prompt2, auth.userId, buildCliHistoryInputConfig({
145339
145674
  prompt: prompt2,
145340
145675
  cliType: session.cliType,
145341
145676
  agentType: session.agentType,
145342
- modeId: modeId ?? void 0,
145343
- modelId: modelId ?? void 0,
145677
+ modeId: dispatchConfig.modeId,
145678
+ modelId: dispatchConfig.modelId,
145679
+ configOptionValues: dispatchConfig.configOptionValues,
145344
145680
  resume: session.acpSessionId ?? void 0
145345
145681
  }));
145346
- let response;
145682
+ const completionAbortController = outputMode === "human" ? void 0 : new AbortController();
145683
+ const completionPromise = outputMode === "human" ? void 0 : waitForTurnCompletion({
145684
+ sessionDoc,
145685
+ userTurnId,
145686
+ outputMode,
145687
+ timeoutMs: resolveStructuredOutputTimeoutMs(options.timeout),
145688
+ signal: completionAbortController?.signal,
145689
+ onEvent: outputMode === "jsonl" ? (event) => printJson(event) : void 0
145690
+ });
145347
145691
  try {
145348
- response = extractChatResponse(await dispatchLocalSessionControl({
145349
- type: "session/chat",
145350
- sessionId,
145351
- machineId: auth.machineId,
145352
- workspaceId: workspace.id,
145353
- ...session.project ? {
145354
- project: session.project
145355
- } : {},
145356
- acpSessionConfig: {
145357
- prompt: prompt2,
145358
- cliType: session.cliType,
145359
- agentType: session.agentType,
145360
- ...modeId ? {
145361
- modeId
145362
- } : {},
145363
- ...modelId ? {
145364
- modelId
145365
- } : {},
145366
- ...session.acpSessionId ? {
145367
- resume: session.acpSessionId
145368
- } : {}
145369
- },
145370
- userTurnId,
145371
- userId: auth.userId,
145372
- userName: auth.userName,
145373
- userEmail: auth.userEmail
145374
- }));
145692
+ await updateSessionActivityTimestampsBestEffort(manager, sessionId);
145375
145693
  } catch (error2) {
145694
+ completionAbortController?.abort();
145695
+ await completionPromise?.catch(() => void 0);
145376
145696
  await removeHistoryEntryById(sessionDoc, userTurnId);
145377
145697
  throw error2;
145378
145698
  }
145379
- await updateSessionActivityTimestampsBestEffort(manager, sessionId);
145380
- if (options.json) {
145381
- printJson({
145382
- ok: true,
145383
- sessionId,
145384
- userTurnId,
145385
- response
145386
- });
145699
+ if (outputMode === "json") {
145700
+ try {
145701
+ if (!completionPromise) {
145702
+ throw new Error("Missing completion promise for structured session chat output.");
145703
+ }
145704
+ const completedTurn = await completionPromise;
145705
+ printJson({
145706
+ ok: true,
145707
+ sessionId,
145708
+ userTurnId,
145709
+ turnId: completedTurn.turnId,
145710
+ content: completedTurn.content,
145711
+ durationMs: completedTurn.durationMs
145712
+ });
145713
+ } catch (error2) {
145714
+ throw buildStructuredWaitError(outputMode, sessionId, userTurnId, error2);
145715
+ }
145716
+ return;
145717
+ }
145718
+ if (outputMode === "jsonl") {
145719
+ try {
145720
+ if (!completionPromise) {
145721
+ throw new Error("Missing completion promise for structured session chat output.");
145722
+ }
145723
+ await completionPromise;
145724
+ } catch (error2) {
145725
+ throw buildStructuredWaitError(outputMode, sessionId, userTurnId, error2);
145726
+ }
145387
145727
  return;
145388
145728
  }
145389
145729
  console.log(userTurnId);
@@ -145405,7 +145745,7 @@ ${entry2.text}`).join("\n\n");
145405
145745
  if (!turnId) {
145406
145746
  throw new Error(`Session ${sessionId} has no active turn to cancel.`);
145407
145747
  }
145408
- const response = extractCancelResponse(await dispatchLocalSessionControl({
145748
+ const response = extractCancelResponse(await dispatchLocalControl({
145409
145749
  type: "session/cancel",
145410
145750
  sessionId,
145411
145751
  machineId: auth.machineId,
@@ -146485,6 +146825,69 @@ ${entry2.text}`).join("\n\n");
146485
146825
  }
146486
146826
  });
146487
146827
  });
146828
+ function sortGitHubRepositories(repositories) {
146829
+ return [
146830
+ ...repositories
146831
+ ].sort((left, right) => {
146832
+ const fullNameCompare = left.fullName.localeCompare(right.fullName);
146833
+ if (fullNameCompare !== 0) {
146834
+ return fullNameCompare;
146835
+ }
146836
+ const nameCompare = left.name.localeCompare(right.name);
146837
+ if (nameCompare !== 0) {
146838
+ return nameCompare;
146839
+ }
146840
+ return left.id - right.id;
146841
+ });
146842
+ }
146843
+ function formatRepositoryVisibility(repository) {
146844
+ return repository.private ? "private" : "public";
146845
+ }
146846
+ function printHumanRepositoryList(repositories) {
146847
+ if (repositories.length === 0) {
146848
+ console.log("No GitHub repositories found.");
146849
+ return;
146850
+ }
146851
+ console.log(renderTerminalTable([
146852
+ {
146853
+ header: "ID"
146854
+ },
146855
+ {
146856
+ header: "Repository"
146857
+ },
146858
+ {
146859
+ header: "Visibility"
146860
+ }
146861
+ ], repositories.map((repository) => [
146862
+ repository.id,
146863
+ repository.fullName,
146864
+ formatRepositoryVisibility(repository)
146865
+ ])));
146866
+ }
146867
+ const githubCommand = new Command("github").description("Manage GitHub repositories linked to a workspace").addCommand(new Command("list").description("List GitHub repositories linked to a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--json", "Print JSON output").option("--debug", "Enable debug output").action(async (options) => {
146868
+ await runOneShotCommand("github", options, async () => {
146869
+ const auth = getAuthContextOrThrow$1("github");
146870
+ const workspace = await resolveWorkspaceOrThrow$1(auth, options.workspace);
146871
+ const repositories = sortGitHubRepositories(await listWorkspaceGitHubRepositoriesForCliToken({
146872
+ token: auth.token,
146873
+ workspaceId: workspace.id
146874
+ }));
146875
+ if (options.json) {
146876
+ printJson({
146877
+ ok: true,
146878
+ workspaceId: workspace.id,
146879
+ repositories: repositories.map((repository) => ({
146880
+ id: repository.id,
146881
+ name: repository.name,
146882
+ fullName: repository.fullName,
146883
+ private: repository.private
146884
+ }))
146885
+ });
146886
+ return;
146887
+ }
146888
+ printHumanRepositoryList(repositories);
146889
+ });
146890
+ }));
146488
146891
  var main = {
146489
146892
  exports: {}
146490
146893
  };
@@ -146824,6 +147227,7 @@ ${entry2.text}`).join("\n\n");
146824
147227
  program.addCommand(agentConfigCommand);
146825
147228
  program.addCommand(machineCommand);
146826
147229
  program.addCommand(exportCommand);
147230
+ program.addCommand(githubCommand);
146827
147231
  program.configureHelp({
146828
147232
  sortSubcommands: true,
146829
147233
  subcommandTerm: (cmd) => cmd.name() + " " + cmd.usage()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.41.7-next.1",
3
+ "version": "0.41.8-next.1",
4
4
  "description": "Lody Agent CLI tool for managing remote command execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",