overlord-cli 0.2608172109.0 → 0.2608180913.0

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.
@@ -9,6 +9,6 @@
9
9
  - `overlord_record_work` (hosted MCP) and `ovld protocol record-work` (CLI) record already-completed chat work as a single review-column mission with a completed objective, file-change rationales, and the standard Gemini delivery summary — no `attach`/`deliver`. Hosted MCP requires `projectId` (it never chooses a project implicitly); the CLI resolves the project from the working directory or `--project-id`. The submission format is in [record-work.md](record-work.md). The route is `POST /api/protocol/record-work`.
10
10
  - `overlord_add_artifact` (hosted MCP) and `ovld protocol add-artifact` (CLI) create a mission artifact during a turn without delivering. Required: `missionId` / `--mission-id`, `type` / `--type`, `label` / `--label`, plus at least one of `contentText` / `--content-text`/`--content-text-file` or `externalUrl` / `--external-url`. Optional `sessionKey` / `--session-key` stamps session/objective provenance. Same service as REST `POST /api/missions/:id/artifacts`. The route is `POST /api/protocol/add-artifact`.
11
11
  - `overlord_update_artifact` (hosted MCP) and `ovld protocol update-artifact` (CLI) revise an existing mission artifact's label, Markdown content, and/or HTTP(S) URL in place. Required: `missionId` / `--mission-id`, `artifactId` / `--artifact-id`, `expectedRevision` / `--expected-revision`. Provide at least one editable field. No session key is required. Same service as REST `PATCH /api/missions/:id/artifacts/:artifactId`. The route is `POST /api/protocol/update-artifact`.
12
- - `overlord_create_mission` / `overlord_add_objectives` accept optional `autoAdvance` (boolean; default false). `overlord_update_objective` (`objectiveId` + `autoAdvance`) and `ovld protocol update-objective --objective-id <id> --auto-advance|--no-auto-advance` toggle it on an existing objective. The route is `POST /api/protocol/update-objective`.
12
+ - `overlord_create_mission` / `overlord_add_objectives` accept optional `autoAdvance` (boolean; default false). `overlord_update_objective` (`objectiveId` plus optional `autoAdvance` and/or `instructionText`) and `ovld protocol update-objective --objective-id <id>` with `--auto-advance|--no-auto-advance` and/or `--instruction-text|--instruction-text-file` update an existing objective. Instruction text is editable only in `draft` or `future` state. The route is `POST /api/protocol/update-objective`.
13
13
  - Objective attachment tools follow the `<verb>_<noun>` MCP naming: `list_attachments`, `prepare_attachment_upload`, `finalize_attachment_upload`, `get_attachment_download_url`, `upload_attachment_file`. CLI commands use `attachment-*`. `mission_id` / `missionId` is optional for attachment calls when the objective or attachment id lets the server derive mission scope.
14
14
  - "Artifacts" may be published mid-turn with `add-artifact` / `overlord_add_artifact`, revised with `update-artifact` / `overlord_update_artifact`, or attached at `deliver` time (next_steps, test_results, migration, decision, note, url) — not user-uploaded files. Files attached by users live on objectives via the attachment tools above.
@@ -198,17 +198,20 @@ const tools = [
198
198
  },
199
199
  {
200
200
  name: 'overlord_update_objective',
201
- title: 'Update objective auto-advance',
201
+ title: 'Update objective',
202
202
  description:
203
- 'Turn auto-advance on or off for an existing objective so delivery can queue the next one.',
203
+ 'Turn auto-advance on or off and/or edit instruction text on a draft or future objective.',
204
204
  inputSchema: objectSchema(
205
205
  {
206
- objectiveId: stringProperty('Objective UUID.'),
206
+ objectiveId: stringProperty('Objective UUID or display id.'),
207
207
  autoAdvance: booleanProperty(
208
208
  'When true, Overlord queues the next objective after this one is delivered. When false, delivery waits for approval.'
209
+ ),
210
+ instructionText: stringProperty(
211
+ 'Replacement instruction text. Allowed only when the objective is in draft or future state; blank clears the text in those states.'
209
212
  )
210
213
  },
211
- ['objectiveId', 'autoAdvance']
214
+ ['objectiveId']
212
215
  )
213
216
  },
214
217
  {
@@ -436,10 +439,15 @@ function callOverlordTool(name, args) {
436
439
  });
437
440
  }
438
441
  if (name === 'overlord_update_objective') {
439
- if (typeof args.autoAdvance !== 'boolean') throw new Error('autoAdvance must be a boolean');
442
+ const hasAutoAdvance = typeof args.autoAdvance === 'boolean';
443
+ const hasInstructionText = typeof args.instructionText === 'string';
444
+ if (!hasAutoAdvance && !hasInstructionText) {
445
+ throw new Error('Provide autoAdvance and/or instructionText');
446
+ }
440
447
  return runProtocol('update-objective', {
441
448
  'objective-id': requiredString(args, 'objectiveId'),
442
- ...(args.autoAdvance ? { 'auto-advance': true } : { 'no-auto-advance': true })
449
+ ...(hasAutoAdvance ? (args.autoAdvance ? { 'auto-advance': true } : { 'no-auto-advance': true }) : {}),
450
+ ...(hasInstructionText ? { 'instruction-text': requiredString(args, 'instructionText') } : {})
443
451
  });
444
452
  }
445
453
  if (name === 'overlord_attach_session') {
package/dist/index.js CHANGED
@@ -17452,6 +17452,41 @@ var init_latch_events = __esm({
17452
17452
  }
17453
17453
  });
17454
17454
 
17455
+ // ../packages/core/dist/service/latch-harness-ingest.js
17456
+ function encodedEventSize(event) {
17457
+ return new TextEncoder().encode(JSON.stringify(event)).length;
17458
+ }
17459
+ function chunkLatchHarnessEventsForIngest({ events, from, maxBytes = HARNESS_EVENT_INGEST_MAX_BYTES }) {
17460
+ if (events.length === 0)
17461
+ return [];
17462
+ const chunks = [];
17463
+ let chunk = [];
17464
+ let chunkFrom = from;
17465
+ let used = 2;
17466
+ for (const event of events) {
17467
+ const size = encodedEventSize(event);
17468
+ const extra = (chunk.length === 0 ? 0 : 1) + size;
17469
+ if (chunk.length > 0 && used + extra > maxBytes) {
17470
+ chunks.push({ events: chunk, from: chunkFrom });
17471
+ chunkFrom += chunk.length;
17472
+ chunk = [event];
17473
+ used = 2 + size;
17474
+ continue;
17475
+ }
17476
+ chunk.push(event);
17477
+ used += extra;
17478
+ }
17479
+ chunks.push({ events: chunk, from: chunkFrom });
17480
+ return chunks;
17481
+ }
17482
+ var HARNESS_EVENT_INGEST_MAX_BYTES;
17483
+ var init_latch_harness_ingest = __esm({
17484
+ "../packages/core/dist/service/latch-harness-ingest.js"() {
17485
+ "use strict";
17486
+ HARNESS_EVENT_INGEST_MAX_BYTES = 64 * 1024;
17487
+ }
17488
+ });
17489
+
17455
17490
  // ../packages/core/dist/service/util.js
17456
17491
  function nowIso() {
17457
17492
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -18917,7 +18952,7 @@ function withWorktreeFields(input, branch) {
18917
18952
  cycle: Number.isFinite(cycleMatch) && cycleMatch > 1 ? cycleMatch : 1
18918
18953
  };
18919
18954
  }
18920
- function planMissionBranch(input) {
18955
+ function planSharedMissionBranch(input) {
18921
18956
  const baseBranch = canonicalMissionBranch(input.mission);
18922
18957
  const allRefs = refSet([...input.refs.local, ...input.refs.remote]);
18923
18958
  const mergedRefs = refSet(input.refs.merged);
@@ -18947,6 +18982,46 @@ function planMissionBranch(input) {
18947
18982
  }
18948
18983
  return { action: "new_cycle", ...withWorktreeFields(input, candidate), from: input.base };
18949
18984
  }
18985
+ function isolationParent(sharedBranch, allRefs, base) {
18986
+ return allRefs.has(sharedBranch) ? sharedBranch : base;
18987
+ }
18988
+ function isolateDecision(input, shared) {
18989
+ const key = sanitizeBranchName(input.isolation?.objectiveKey ?? "", "");
18990
+ if (!key) return shared;
18991
+ const allRefs = refSet([...input.refs.local, ...input.refs.remote]);
18992
+ const mergedRefs = refSet(input.refs.merged);
18993
+ const checkedOutRefs = refSet(input.refs.checkedOut ?? []);
18994
+ const isolatedBase = sanitizeBranchName(`${shared.branch}-${key}`, shared.branch);
18995
+ if (isolatedBase === shared.branch) return shared;
18996
+ if (!allRefs.has(isolatedBase)) {
18997
+ return {
18998
+ action: "create",
18999
+ ...withWorktreeFields(input, isolatedBase),
19000
+ cycle: 1,
19001
+ from: isolationParent(shared.branch, allRefs, input.base)
19002
+ };
19003
+ }
19004
+ if (!mergedRefs.has(isolatedBase)) {
19005
+ return { action: "reuse", ...withWorktreeFields(input, isolatedBase), cycle: 1 };
19006
+ }
19007
+ let cycle = Math.max(2, highestExistingCycle(isolatedBase, allRefs) + 1);
19008
+ let candidate = `${isolatedBase}-${cycle}`;
19009
+ while (allRefs.has(candidate) || checkedOutRefs.has(candidate) || mergedRefs.has(candidate)) {
19010
+ cycle += 1;
19011
+ candidate = `${isolatedBase}-${cycle}`;
19012
+ }
19013
+ return {
19014
+ action: "new_cycle",
19015
+ ...withWorktreeFields(input, candidate),
19016
+ cycle,
19017
+ from: isolationParent(shared.branch, allRefs, input.base)
19018
+ };
19019
+ }
19020
+ function planMissionBranch(input) {
19021
+ const shared = planSharedMissionBranch(input);
19022
+ if (!input.isolation || input.overrideBranch) return shared;
19023
+ return isolateDecision(input, shared);
19024
+ }
18950
19025
  var TITLE_SLUG_MAX;
18951
19026
  var init_branch_planning = __esm({
18952
19027
  "src/branch-planning.ts"() {
@@ -19195,6 +19270,48 @@ function resolveLaunchResourceKey({
19195
19270
  }
19196
19271
  return deriveProjectResourceKey({ directoryPath: options.workingDirectory });
19197
19272
  }
19273
+ function readString(value) {
19274
+ return typeof value === "string" ? value.trim() : "";
19275
+ }
19276
+ async function resolveProjectPrimaryResourceKey({
19277
+ runtime,
19278
+ mission
19279
+ }) {
19280
+ const projectId = readString(mission.projectId);
19281
+ if (!projectId) return DEFAULT_PRIMARY_RESOURCE_KEY;
19282
+ try {
19283
+ const resources = await runtime.backend.get(
19284
+ `/api/projects/${encodeURIComponent(projectId)}/resources`
19285
+ );
19286
+ if (!Array.isArray(resources)) return DEFAULT_PRIMARY_RESOURCE_KEY;
19287
+ const primary = resources.find((resource) => resource?.isPrimary === true);
19288
+ return readString(primary?.resourceKey) || DEFAULT_PRIMARY_RESOURCE_KEY;
19289
+ } catch {
19290
+ return DEFAULT_PRIMARY_RESOURCE_KEY;
19291
+ }
19292
+ }
19293
+ async function resolveBranchIsolation({
19294
+ runtime,
19295
+ mission,
19296
+ options,
19297
+ useWorktree
19298
+ }) {
19299
+ if (!useWorktree) return null;
19300
+ if (mission.allowParallelObjectives !== true) return null;
19301
+ const objectiveId = readString(options.objectiveId);
19302
+ if (!objectiveId) return null;
19303
+ const objectives = Array.isArray(mission.objectives) ? mission.objectives : [];
19304
+ const self = objectives.find((candidate) => readString(candidate.id) === objectiveId);
19305
+ const objectiveKey = readString(self?.displayKey);
19306
+ if (!self || !objectiveKey) return null;
19307
+ const primaryResourceKey = await resolveProjectPrimaryResourceKey({ runtime, mission });
19308
+ const canonical = (value) => readString(value) || primaryResourceKey;
19309
+ const selfResourceKey = canonical(self.resourceKey);
19310
+ const hasLiveSibling = objectives.some(
19311
+ (candidate) => readString(candidate.id) !== objectiveId && PARALLEL_BLOCKING_OBJECTIVE_STATES.includes(readString(candidate.state)) && canonical(candidate.resourceKey) === selfResourceKey
19312
+ );
19313
+ return hasLiveSibling ? { objectiveKey } : null;
19314
+ }
19198
19315
  async function prepareMissionBranch({
19199
19316
  runtime,
19200
19317
  options
@@ -19221,6 +19338,7 @@ async function prepareMissionBranch({
19221
19338
  const overrideBranch = overrideFlag || missionOverrideBranch(mission);
19222
19339
  const projectSlug = await resolveMissionProjectSlug({ runtime, mission });
19223
19340
  const resourceKey = resolveLaunchResourceKey({ mission, options });
19341
+ const isolation = await resolveBranchIsolation({ runtime, mission, options, useWorktree });
19224
19342
  const decision = planMissionBranch({
19225
19343
  mission: {
19226
19344
  title: typeof mission.title === "string" ? mission.title : "mission",
@@ -19232,8 +19350,10 @@ async function prepareMissionBranch({
19232
19350
  base,
19233
19351
  refs,
19234
19352
  worktreeRoot: resolveWorktreeRoot(),
19235
- overrideBranch
19353
+ overrideBranch,
19354
+ isolation
19236
19355
  });
19356
+ const isolated = Boolean(isolation) && !overrideBranch;
19237
19357
  if (useWorktree) {
19238
19358
  ensureWorktree(gitRoot, decision);
19239
19359
  return {
@@ -19244,7 +19364,8 @@ async function prepareMissionBranch({
19244
19364
  worktreePath: decision.worktreePath,
19245
19365
  resourceKey,
19246
19366
  action: decision.action,
19247
- cycle: decision.cycle
19367
+ cycle: decision.cycle,
19368
+ isolated
19248
19369
  }
19249
19370
  };
19250
19371
  }
@@ -19257,15 +19378,19 @@ async function prepareMissionBranch({
19257
19378
  worktreePath: gitRoot,
19258
19379
  resourceKey,
19259
19380
  action: decision.action,
19260
- cycle: decision.cycle
19381
+ cycle: decision.cycle,
19382
+ isolated: false
19261
19383
  }
19262
19384
  };
19263
19385
  }
19386
+ var PARALLEL_BLOCKING_OBJECTIVE_STATES, DEFAULT_PRIMARY_RESOURCE_KEY;
19264
19387
  var init_branch_preparation = __esm({
19265
19388
  "src/branch-preparation.ts"() {
19266
19389
  "use strict";
19267
19390
  init_project_resource_key();
19268
19391
  init_branch_planning();
19392
+ PARALLEL_BLOCKING_OBJECTIVE_STATES = ["launching", "executing", "pending_delivery"];
19393
+ DEFAULT_PRIMARY_RESOURCE_KEY = "primary";
19269
19394
  }
19270
19395
  });
19271
19396
 
@@ -20673,7 +20798,7 @@ Subcommands:
20673
20798
  search-missions Find missions by keyword, status, or project
20674
20799
  discuss-objective Mark a draft objective as submitted (does not start execution)
20675
20800
  add-objectives Append ordered objectives to an existing mission
20676
- update-objective Set auto-advance on an existing objective
20801
+ update-objective Set auto-advance and/or instruction text on an objective
20677
20802
  create Create a draft mission without attaching
20678
20803
  prompt Create a mission and attach to it immediately
20679
20804
  record-work Record completed-from-chat work as a review mission (no attach)
@@ -20798,12 +20923,17 @@ add-objectives:
20798
20923
 
20799
20924
  update-objective:
20800
20925
  Purpose:
20801
- Turn auto-advance on or off for an existing objective.
20926
+ Turn auto-advance on or off and/or edit instruction text on an existing objective.
20802
20927
  Required:
20803
20928
  --objective-id <id>
20804
- --auto-advance or --no-auto-advance
20929
+ At least one of:
20930
+ --auto-advance or --no-auto-advance
20931
+ --instruction-text <text> or --instruction-text-file <path|->
20932
+ Rules:
20933
+ Instruction text may be edited only when the objective is in draft or future state.
20934
+ Blank instruction text is allowed in those states.
20805
20935
  Returns:
20806
- The updated objective JSON, including autoAdvance.
20936
+ The updated objective JSON, including autoAdvance and instructionText.
20807
20937
 
20808
20938
  create:
20809
20939
  Purpose:
@@ -24241,14 +24371,20 @@ async function postLatchHarnessEventsAfterLaunch({
24241
24371
  from: 0
24242
24372
  });
24243
24373
  if (collected.events.length === 0) return;
24244
- await backend.post({
24245
- path: `/api/runner/requests/${requestId}/harness-events`,
24246
- body: {
24247
- providerSessionId,
24248
- events: collected.events,
24249
- from: collected.from
24250
- }
24374
+ const chunks = chunkLatchHarnessEventsForIngest({
24375
+ events: collected.events,
24376
+ from: collected.from
24251
24377
  });
24378
+ for (const chunk of chunks) {
24379
+ await backend.post({
24380
+ path: `/api/runner/requests/${requestId}/harness-events`,
24381
+ body: {
24382
+ providerSessionId,
24383
+ events: chunk.events,
24384
+ from: chunk.from
24385
+ }
24386
+ });
24387
+ }
24252
24388
  } catch {
24253
24389
  }
24254
24390
  }
@@ -24258,6 +24394,7 @@ var init_commands = __esm({
24258
24394
  "use strict";
24259
24395
  init_dist2();
24260
24396
  init_latch_events();
24397
+ init_latch_harness_ingest();
24261
24398
  init_project_metadata();
24262
24399
  init_local_target_mutation_runner();
24263
24400
  init_terminal_profile_types2();