overlord-cli 0.2605111118.0 → 0.2605120852.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.
- package/README.md +1 -1
- package/bin/_cli/protocol.mjs +284 -18
- package/package.json +1 -1
- package/plugins/claude/skills/overlord-ticket/SKILL.md +20 -11
- package/plugins/cursor/scripts/overlord-mcp.mjs +14 -9
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +115 -9
- package/plugins/overlord/README.md +3 -3
- package/plugins/overlord/scripts/overlord-mcp.mjs +72 -49
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +42 -30
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
119
119
|
- `permission-request` - notify Overlord that the agent is requesting tool permission
|
|
120
120
|
- `read-context` - read shared persistent context for this ticket
|
|
121
121
|
- `write-context` - write shared persistent context for future sessions
|
|
122
|
-
- `deliver` -
|
|
122
|
+
- `deliver` - create a local JJ/Git checkpoint when available, send artifacts/rationales, and move the ticket to review
|
|
123
123
|
- `attachment-prepare-upload` - get a signed upload URL for an objective attachment
|
|
124
124
|
- `attachment-finalize-upload` - finalize an uploaded objective attachment row after storage upload
|
|
125
125
|
- `attachment-download-url` - get a signed download URL for an existing objective attachment
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import path from 'node:path';
|
|
|
7
7
|
|
|
8
8
|
import { buildAuthHeaders, getAuthStatus, resolveAuth } from './credentials.mjs';
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
/**
|
|
12
11
|
* Parse simple CLI flags: --key value or --key=value
|
|
13
12
|
* @param {string[]} args
|
|
@@ -359,6 +358,238 @@ async function resolveSnapshotContext(flags) {
|
|
|
359
358
|
return null;
|
|
360
359
|
}
|
|
361
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Shallow-merge snapshot metadata: launch/env context first, then deliver payload
|
|
363
|
+
* (payload wins on duplicate keys) so agents can add provenance details without
|
|
364
|
+
* losing launch context from OVERLORD_SNAPSHOT_JSON.
|
|
365
|
+
* @param {Record<string, unknown> | null | undefined} fromEnvOrFlags
|
|
366
|
+
* @param {Record<string, unknown> | null | undefined} fromPayload
|
|
367
|
+
* @returns {Record<string, unknown> | null}
|
|
368
|
+
*/
|
|
369
|
+
function mergeDeliverSnapshot(fromEnvOrFlags, fromPayload) {
|
|
370
|
+
const base =
|
|
371
|
+
fromEnvOrFlags && typeof fromEnvOrFlags === 'object' && !Array.isArray(fromEnvOrFlags)
|
|
372
|
+
? { ...fromEnvOrFlags }
|
|
373
|
+
: {};
|
|
374
|
+
const overlay =
|
|
375
|
+
fromPayload && typeof fromPayload === 'object' && !Array.isArray(fromPayload)
|
|
376
|
+
? { ...fromPayload }
|
|
377
|
+
: {};
|
|
378
|
+
const merged = { ...base, ...overlay };
|
|
379
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function runLocalCommand(command, args, options = {}) {
|
|
383
|
+
return execFileSync(command, args, {
|
|
384
|
+
cwd: options.cwd,
|
|
385
|
+
encoding: 'utf8',
|
|
386
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
387
|
+
timeout: options.timeoutMs ?? 60000,
|
|
388
|
+
maxBuffer: 10 * 1024 * 1024
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resolveGitRepoRoot(workspacePath) {
|
|
393
|
+
try {
|
|
394
|
+
return runLocalCommand('git', ['-C', workspacePath, 'rev-parse', '--show-toplevel'], {
|
|
395
|
+
cwd: workspacePath,
|
|
396
|
+
timeoutMs: 15000
|
|
397
|
+
}).trim();
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Snapshot the working tree to a hidden ref refs/overlord/checkpoints/<objectiveId>.
|
|
405
|
+
* Captures tracked + untracked + staged + unstaged in one commit-tree object.
|
|
406
|
+
* Idempotent: if the ref already exists, returns the existing sha.
|
|
407
|
+
* Returns null when not in a git repo or when no objectiveId is available.
|
|
408
|
+
*/
|
|
409
|
+
function createLocalCheckpoint({ flags, kind = 'objective', objectiveId, snapshot = null }) {
|
|
410
|
+
if (flags['skip-checkpoint']) return null;
|
|
411
|
+
|
|
412
|
+
const workspacePath = path.resolve(
|
|
413
|
+
String(snapshot?.workspacePath ?? process.env.OVERLORD_WORKSPACE_PATH ?? process.cwd())
|
|
414
|
+
);
|
|
415
|
+
const repoRoot = fs.existsSync(workspacePath) ? resolveGitRepoRoot(workspacePath) : null;
|
|
416
|
+
if (!repoRoot) return null;
|
|
417
|
+
|
|
418
|
+
const resolvedObjectiveId =
|
|
419
|
+
objectiveId ?? snapshot?.objectiveId ?? process.env.OVERLORD_OBJECTIVE_ID ?? null;
|
|
420
|
+
if (!resolvedObjectiveId || !/^[A-Za-z0-9_-]+$/.test(String(resolvedObjectiveId))) {
|
|
421
|
+
// Without an objective id we cannot name the ref deterministically.
|
|
422
|
+
// Fall back to recording HEAD only so the protocol still has provenance.
|
|
423
|
+
try {
|
|
424
|
+
const gitCommitId = runLocalCommand('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], {
|
|
425
|
+
cwd: repoRoot
|
|
426
|
+
}).trim();
|
|
427
|
+
const diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', 'HEAD'], {
|
|
428
|
+
cwd: repoRoot
|
|
429
|
+
}).trim();
|
|
430
|
+
return {
|
|
431
|
+
checkpoint: { diffStat: diffStat || null, kind },
|
|
432
|
+
snapshot: {
|
|
433
|
+
gitCommitId: gitCommitId || null,
|
|
434
|
+
headSha: gitCommitId || null,
|
|
435
|
+
diffStat: diffStat || null
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
} catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const ref = `refs/overlord/checkpoints/${resolvedObjectiveId}`;
|
|
444
|
+
const headSha = runLocalCommand('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], {
|
|
445
|
+
cwd: repoRoot
|
|
446
|
+
}).trim();
|
|
447
|
+
|
|
448
|
+
// If the ref already exists, return the existing snapshot.
|
|
449
|
+
let existingSha = '';
|
|
450
|
+
try {
|
|
451
|
+
existingSha = runLocalCommand('git', ['-C', repoRoot, 'rev-parse', '--verify', ref], {
|
|
452
|
+
cwd: repoRoot
|
|
453
|
+
}).trim();
|
|
454
|
+
} catch {
|
|
455
|
+
/* not present */
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (existingSha) {
|
|
459
|
+
let diffStat = '';
|
|
460
|
+
try {
|
|
461
|
+
diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${existingSha}^!`], {
|
|
462
|
+
cwd: repoRoot
|
|
463
|
+
}).trim();
|
|
464
|
+
} catch {
|
|
465
|
+
/* ignore */
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
checkpoint: { diffStat: diffStat || null, kind },
|
|
469
|
+
snapshot: {
|
|
470
|
+
gitCommitId: existingSha,
|
|
471
|
+
gitRefName: ref,
|
|
472
|
+
headSha,
|
|
473
|
+
objectiveId: String(resolvedObjectiveId),
|
|
474
|
+
diffStat: diffStat || null
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Build a tree from the current working copy without polluting the user's
|
|
480
|
+
// index by writing to a temporary index file.
|
|
481
|
+
const tempIndex = path.join(repoRoot, '.git', `overlord-snap-index-${Date.now()}`);
|
|
482
|
+
const env = { ...process.env, NO_COLOR: '1', GIT_INDEX_FILE: tempIndex };
|
|
483
|
+
try {
|
|
484
|
+
execFileSync('git', ['-C', repoRoot, 'read-tree', 'HEAD'], { cwd: repoRoot, env });
|
|
485
|
+
execFileSync('git', ['-C', repoRoot, 'add', '-A'], { cwd: repoRoot, env });
|
|
486
|
+
const tree = execFileSync('git', ['-C', repoRoot, 'write-tree'], {
|
|
487
|
+
cwd: repoRoot,
|
|
488
|
+
env,
|
|
489
|
+
encoding: 'utf8'
|
|
490
|
+
}).trim();
|
|
491
|
+
if (!tree) throw new Error('git write-tree produced no output.');
|
|
492
|
+
const commit = execFileSync(
|
|
493
|
+
'git',
|
|
494
|
+
[
|
|
495
|
+
'-C',
|
|
496
|
+
repoRoot,
|
|
497
|
+
'commit-tree',
|
|
498
|
+
tree,
|
|
499
|
+
'-p',
|
|
500
|
+
headSha,
|
|
501
|
+
'-m',
|
|
502
|
+
`overlord checkpoint ${resolvedObjectiveId}`
|
|
503
|
+
],
|
|
504
|
+
{ cwd: repoRoot, env: { ...process.env, NO_COLOR: '1' }, encoding: 'utf8' }
|
|
505
|
+
).trim();
|
|
506
|
+
if (!commit) throw new Error('git commit-tree produced no output.');
|
|
507
|
+
execFileSync('git', ['-C', repoRoot, 'update-ref', ref, commit], {
|
|
508
|
+
cwd: repoRoot
|
|
509
|
+
});
|
|
510
|
+
let diffStat = '';
|
|
511
|
+
try {
|
|
512
|
+
diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${commit}^!`], {
|
|
513
|
+
cwd: repoRoot
|
|
514
|
+
}).trim();
|
|
515
|
+
} catch {
|
|
516
|
+
/* ignore */
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
checkpoint: { diffStat: diffStat || null, kind },
|
|
520
|
+
snapshot: {
|
|
521
|
+
gitCommitId: commit,
|
|
522
|
+
gitRefName: ref,
|
|
523
|
+
headSha,
|
|
524
|
+
objectiveId: String(resolvedObjectiveId),
|
|
525
|
+
diffStat: diffStat || null
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
} catch (error) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
`Failed to create git checkpoint in ${repoRoot}: ${
|
|
531
|
+
error instanceof Error ? error.message : String(error)
|
|
532
|
+
}\nRe-run attach with --skip-checkpoint to proceed without local provenance.`
|
|
533
|
+
);
|
|
534
|
+
} finally {
|
|
535
|
+
try {
|
|
536
|
+
fs.unlinkSync(tempIndex);
|
|
537
|
+
} catch {
|
|
538
|
+
/* ignore */
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function createAndRecordPendingCheckpoints({
|
|
544
|
+
attachData,
|
|
545
|
+
bearerToken,
|
|
546
|
+
flags,
|
|
547
|
+
localSecret,
|
|
548
|
+
organizationId,
|
|
549
|
+
platformUrl,
|
|
550
|
+
ticketId,
|
|
551
|
+
timeoutMs
|
|
552
|
+
}) {
|
|
553
|
+
const pendingObjectiveIds = Array.isArray(attachData.pendingCheckpointObjectiveIds)
|
|
554
|
+
? attachData.pendingCheckpointObjectiveIds.filter(id => typeof id === 'string' && id.trim())
|
|
555
|
+
: [];
|
|
556
|
+
const sessionKey = attachData.session?.sessionKey;
|
|
557
|
+
if (!sessionKey || pendingObjectiveIds.length === 0) return [];
|
|
558
|
+
|
|
559
|
+
const recorded = [];
|
|
560
|
+
for (const objectiveId of pendingObjectiveIds) {
|
|
561
|
+
const checkpointResult = createLocalCheckpoint({
|
|
562
|
+
flags,
|
|
563
|
+
kind: 'objective',
|
|
564
|
+
objectiveId: objectiveId.trim()
|
|
565
|
+
});
|
|
566
|
+
if (!checkpointResult?.snapshot) {
|
|
567
|
+
process.stderr.write(
|
|
568
|
+
`[protocol] Skipped checkpoint for objective ${objectiveId}: no git repository found at ${process.cwd()}.\n`
|
|
569
|
+
);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
await apiPost(
|
|
574
|
+
platformUrl,
|
|
575
|
+
bearerToken,
|
|
576
|
+
localSecret,
|
|
577
|
+
organizationId,
|
|
578
|
+
'/api/protocol/update',
|
|
579
|
+
{
|
|
580
|
+
sessionKey,
|
|
581
|
+
ticketId,
|
|
582
|
+
summary: `Created local git checkpoint for objective ${objectiveId}.`,
|
|
583
|
+
phase: 'execute',
|
|
584
|
+
snapshot: checkpointResult.snapshot
|
|
585
|
+
},
|
|
586
|
+
timeoutMs
|
|
587
|
+
);
|
|
588
|
+
recorded.push(objectiveId);
|
|
589
|
+
}
|
|
590
|
+
return recorded;
|
|
591
|
+
}
|
|
592
|
+
|
|
362
593
|
function normalizeRepoRelativeFilePath(filePath, repoRoot) {
|
|
363
594
|
if (typeof filePath !== 'string') return null;
|
|
364
595
|
|
|
@@ -534,7 +765,8 @@ function resolveExternalSessionId(flags) {
|
|
|
534
765
|
async function protocolAttach(args) {
|
|
535
766
|
const flags = parseFlags(args);
|
|
536
767
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
537
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
768
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
769
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
538
770
|
const timeoutMs = resolveTimeout(flags);
|
|
539
771
|
|
|
540
772
|
const externalSessionId = resolveExternalSessionId(flags);
|
|
@@ -557,6 +789,20 @@ async function protocolAttach(args) {
|
|
|
557
789
|
timeoutMs
|
|
558
790
|
);
|
|
559
791
|
|
|
792
|
+
const recordedCheckpointObjectiveIds = await createAndRecordPendingCheckpoints({
|
|
793
|
+
attachData: data,
|
|
794
|
+
bearerToken,
|
|
795
|
+
flags,
|
|
796
|
+
localSecret,
|
|
797
|
+
organizationId,
|
|
798
|
+
platformUrl,
|
|
799
|
+
ticketId,
|
|
800
|
+
timeoutMs
|
|
801
|
+
});
|
|
802
|
+
if (recordedCheckpointObjectiveIds.length > 0) {
|
|
803
|
+
data.recordedCheckpointObjectiveIds = recordedCheckpointObjectiveIds;
|
|
804
|
+
}
|
|
805
|
+
|
|
560
806
|
const sessionKey = data.session?.sessionKey;
|
|
561
807
|
console.log(JSON.stringify(data, null, 2));
|
|
562
808
|
|
|
@@ -580,7 +826,8 @@ async function protocolUpdate(args) {
|
|
|
580
826
|
? readTextFile(String(flags['summary-file']), '--summary-file')
|
|
581
827
|
: requireFlag(flags, 'summary', undefined);
|
|
582
828
|
|
|
583
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
829
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
830
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
584
831
|
const timeoutMs = resolveTimeout(flags);
|
|
585
832
|
const changeRationales = await resolveChangeRationales(flags);
|
|
586
833
|
const externalSessionId = resolveExternalSessionId(flags);
|
|
@@ -637,7 +884,8 @@ async function protocolRecordChangeRationales(args) {
|
|
|
637
884
|
);
|
|
638
885
|
}
|
|
639
886
|
|
|
640
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
887
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
888
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
641
889
|
const timeoutMs = resolveTimeout(flags);
|
|
642
890
|
const snapshot = await resolveSnapshotContext(flags);
|
|
643
891
|
|
|
@@ -679,7 +927,8 @@ async function protocolAsk(args) {
|
|
|
679
927
|
? readTextFile(String(flags['question-file']), '--question-file')
|
|
680
928
|
: requireFlag(flags, 'question', undefined);
|
|
681
929
|
|
|
682
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
930
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
931
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
683
932
|
const timeoutMs = resolveTimeout(flags);
|
|
684
933
|
|
|
685
934
|
const body = {
|
|
@@ -715,7 +964,8 @@ async function protocolPermissionRequest(args) {
|
|
|
715
964
|
? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
|
|
716
965
|
: {};
|
|
717
966
|
|
|
718
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
967
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
968
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
719
969
|
const timeoutMs = resolveTimeout(flags);
|
|
720
970
|
|
|
721
971
|
const data = await apiPost(
|
|
@@ -740,7 +990,8 @@ async function protocolReadContext(args) {
|
|
|
740
990
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
741
991
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
742
992
|
|
|
743
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
993
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
994
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
744
995
|
const timeoutMs = resolveTimeout(flags);
|
|
745
996
|
|
|
746
997
|
const body = {
|
|
@@ -784,7 +1035,8 @@ async function protocolWriteContext(args) {
|
|
|
784
1035
|
value = String(flags.value);
|
|
785
1036
|
}
|
|
786
1037
|
|
|
787
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1038
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1039
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
788
1040
|
const timeoutMs = resolveTimeout(flags);
|
|
789
1041
|
|
|
790
1042
|
const body = {
|
|
@@ -831,7 +1083,8 @@ async function protocolDeliver(args) {
|
|
|
831
1083
|
? readTextFile(String(flags['summary-file']), '--summary-file')
|
|
832
1084
|
: requireFlag(flags, 'summary', undefined));
|
|
833
1085
|
|
|
834
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1086
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1087
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
835
1088
|
const timeoutMs = resolveTimeout(flags);
|
|
836
1089
|
|
|
837
1090
|
let artifacts = deliverPayload?.artifacts ?? [];
|
|
@@ -853,7 +1106,10 @@ async function protocolDeliver(args) {
|
|
|
853
1106
|
|
|
854
1107
|
const changeRationales =
|
|
855
1108
|
deliverPayload?.changeRationales ?? (await resolveChangeRationales(flags));
|
|
856
|
-
const snapshot = (
|
|
1109
|
+
const snapshot = mergeDeliverSnapshot(
|
|
1110
|
+
await resolveSnapshotContext(flags),
|
|
1111
|
+
deliverPayload?.snapshot ?? null
|
|
1112
|
+
);
|
|
857
1113
|
validateDeliverFileChanges(flags, changeRationales);
|
|
858
1114
|
|
|
859
1115
|
const body = {
|
|
@@ -887,7 +1143,8 @@ async function protocolAttachmentList(args) {
|
|
|
887
1143
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
888
1144
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
889
1145
|
|
|
890
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1146
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1147
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
891
1148
|
const timeoutMs = resolveTimeout(flags);
|
|
892
1149
|
|
|
893
1150
|
const body = {
|
|
@@ -916,7 +1173,8 @@ async function protocolAttachmentPrepareUpload(args) {
|
|
|
916
1173
|
const objectiveId = requireFlag(flags, 'objective-id', undefined);
|
|
917
1174
|
const fileName = requireFlag(flags, 'file-name', undefined);
|
|
918
1175
|
|
|
919
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1176
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1177
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
920
1178
|
const timeoutMs = resolveTimeout(flags);
|
|
921
1179
|
|
|
922
1180
|
const body = {
|
|
@@ -953,7 +1211,8 @@ async function protocolAttachmentFinalizeUpload(args) {
|
|
|
953
1211
|
const storagePath = requireFlag(flags, 'storage-path', undefined);
|
|
954
1212
|
const label = requireFlag(flags, 'label', undefined);
|
|
955
1213
|
|
|
956
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1214
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1215
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
957
1216
|
const timeoutMs = resolveTimeout(flags);
|
|
958
1217
|
|
|
959
1218
|
const body = {
|
|
@@ -993,7 +1252,8 @@ async function protocolAttachmentGetDownloadUrl(args) {
|
|
|
993
1252
|
throw new Error('--objective-id is required when using --storage-path');
|
|
994
1253
|
}
|
|
995
1254
|
|
|
996
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1255
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1256
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
997
1257
|
const timeoutMs = resolveTimeout(flags);
|
|
998
1258
|
|
|
999
1259
|
const body = {
|
|
@@ -1034,7 +1294,8 @@ async function protocolAttachmentUploadFile(args) {
|
|
|
1034
1294
|
const fileStats = await stat(filePath);
|
|
1035
1295
|
const fileBytes = await readFile(filePath);
|
|
1036
1296
|
|
|
1037
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1297
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1298
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1038
1299
|
const timeoutMs = resolveTimeout(flags);
|
|
1039
1300
|
|
|
1040
1301
|
const metadata = flags['metadata-json']
|
|
@@ -1125,7 +1386,8 @@ async function protocolDiscoverProject(args) {
|
|
|
1125
1386
|
async function protocolConnect(args) {
|
|
1126
1387
|
const flags = parseFlags(args);
|
|
1127
1388
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
1128
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1389
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1390
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1129
1391
|
const timeoutMs = resolveTimeout(flags);
|
|
1130
1392
|
|
|
1131
1393
|
const body = {
|
|
@@ -1160,7 +1422,8 @@ async function protocolConnect(args) {
|
|
|
1160
1422
|
async function protocolLoadContext(args) {
|
|
1161
1423
|
const flags = parseFlags(args);
|
|
1162
1424
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
1163
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1425
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1426
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1164
1427
|
const timeoutMs = resolveTimeout(flags);
|
|
1165
1428
|
|
|
1166
1429
|
const body = { ticketId };
|
|
@@ -1253,7 +1516,8 @@ async function protocolCreateTicket(args) {
|
|
|
1253
1516
|
const flags = parseFlags(args);
|
|
1254
1517
|
const { sessionKey, ticketId } = resolveSessionFlags(flags);
|
|
1255
1518
|
const objective = requireFlag(flags, 'objective', undefined);
|
|
1256
|
-
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1519
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1520
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1257
1521
|
const timeoutMs = resolveTimeout(flags);
|
|
1258
1522
|
const agentIdentifier = resolveProtocolAgentIdentifier(flags);
|
|
1259
1523
|
const modelIdentifier = resolveProtocolModelIdentifier(flags);
|
|
@@ -1496,10 +1760,12 @@ attach:
|
|
|
1496
1760
|
--method <connectionMethod>
|
|
1497
1761
|
--external-session-id <id|null> Store the native agent thread/session id, or clear it with null
|
|
1498
1762
|
--metadata-json <json> Extra session metadata object
|
|
1763
|
+
--skip-checkpoint Bypass automatic objective-start git checkpoint creation
|
|
1499
1764
|
Returns:
|
|
1500
1765
|
Full JSON including session.sessionKey, ticket, history, artifacts, sharedState, and promptContext
|
|
1501
1766
|
Notes:
|
|
1502
1767
|
If --external-session-id is omitted, the CLI may auto-detect Codex or Claude session ids
|
|
1768
|
+
Attach creates and records missing local git checkpoints for executing objectives before work starts.
|
|
1503
1769
|
|
|
1504
1770
|
connect:
|
|
1505
1771
|
Purpose:
|
package/package.json
CHANGED
|
@@ -65,7 +65,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
65
65
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
68
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
69
69
|
|
|
70
70
|
## Mode 2: Asked From Chat To Use Overlord
|
|
71
71
|
|
|
@@ -73,14 +73,15 @@ Use this mode when the conversation starts normally and the user asks Claude to
|
|
|
73
73
|
|
|
74
74
|
1. If the user wants to create tickets (and does not ask to start execution), run `ovld protocol create --agent claude-code --objective "..."`.
|
|
75
75
|
- When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
|
|
76
|
-
- When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to
|
|
77
|
-
2. Default to `create` for new tickets. Only use `/overlord:
|
|
78
|
-
`
|
|
76
|
+
- When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
|
|
77
|
+
2. Default to `create` for new tickets. Only use `/overlord:prompt` or `ovld protocol prompt --agent claude-code --objective "..."` when the user explicitly asks to create and execute immediately.
|
|
78
|
+
`prompt` creates the ticket in `execute` status and attaches immediately.
|
|
79
79
|
3. If the user already has a ticket ID and only wants to inspect it, use `/overlord:load` or run `ovld protocol load-context --ticket-id <ticket_id>`.
|
|
80
80
|
4. If the user wants to route the current session onto an existing ticket by ID, use `/overlord:connect` or run `ovld protocol connect --ticket-id <ticket_id>`.
|
|
81
|
-
5. If the user wants to
|
|
82
|
-
6. If
|
|
83
|
-
7.
|
|
81
|
+
5. If the user wants to establish a persistent session with a ticket by ID, use `/overlord:attach` or run `ovld protocol attach --ticket-id <ticket_id>`.
|
|
82
|
+
6. If the user wants to find a ticket but does not know the ID, use `ovld attach` for interactive ticket search and agent launch, or run `ovld protocol search-tickets --query "..." --status next-up,execute` and ask the user to confirm.
|
|
83
|
+
7. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
|
|
84
|
+
8. Once you attach to a ticket, switch back to Mode 1 and follow the full ticket lifecycle.
|
|
84
85
|
|
|
85
86
|
## Change Rationales
|
|
86
87
|
|
|
@@ -108,7 +109,7 @@ Record only meaningful behavioral changes. Skip formatting-only noise.
|
|
|
108
109
|
|
|
109
110
|
When creating tickets from within a repository:
|
|
110
111
|
- Prefer `create` by default for draft ticket creation.
|
|
111
|
-
- Use `
|
|
112
|
+
- Use `prompt` only when the user explicitly asks to start execution immediately.
|
|
112
113
|
- Both commands can resolve the project from the current working directory; use `--working-directory` to override.
|
|
113
114
|
|
|
114
115
|
```bash
|
|
@@ -116,7 +117,7 @@ ovld protocol create --agent claude-code --objective "Capture follow-up work fro
|
|
|
116
117
|
```
|
|
117
118
|
|
|
118
119
|
```bash
|
|
119
|
-
ovld protocol
|
|
120
|
+
ovld protocol prompt --agent claude-code --objective "Implement feature X" --priority medium
|
|
120
121
|
```
|
|
121
122
|
|
|
122
123
|
To inspect project resolution explicitly:
|
|
@@ -148,6 +149,15 @@ ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TI
|
|
|
148
149
|
|
|
149
150
|
The `attach` and `load-context` responses already include `attachments` and `objectives` arrays — use those for `<attachment-id>` and `<objective-id>` values. Run `attachment-list` mid-session if new files have been uploaded since attach.
|
|
150
151
|
|
|
152
|
+
## Defaults And Notes
|
|
153
|
+
|
|
154
|
+
- API requires `agentIdentifier` and `connectionMethod` on attach/connect/prompt. The CLI defaults them to `claude-code`/`cli`; the MCP tool defaults to `mcp`. Override with `--agent` / `--method` when calling from a different runtime.
|
|
155
|
+
- Hosted Overlord MCP (`/functions/v1/mcp`) uses the same canonical tool names as any local MCP shim that shells into `ovld protocol` (`attach`, `update`, `deliver`, …). Hosted calls use camelCase JSON keys (`ticketId`, `sessionKey`) matching `POST /api/protocol/*` bodies; the local shim uses snake_case keys mapped to CLI flags (`ticket_id`, `session_key`).
|
|
156
|
+
- `permission-request` is invoked by the Claude Code permission hook installed by the bundle. Agents do not normally call it directly.
|
|
157
|
+
- `record_change_rationales` (MCP) and `ovld protocol record-change-rationales` (CLI) both write to the same `file_changes` table. The dedicated CLI route is `POST /api/protocol/record-change-rationales`.
|
|
158
|
+
- 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-*` and require `--objective-id` for upload/finalize.
|
|
159
|
+
- "Artifacts" in `deliver` are the structured records an agent submits at delivery 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.
|
|
160
|
+
|
|
151
161
|
## Rules
|
|
152
162
|
|
|
153
163
|
- Always attach first and always deliver last once you are on a ticket.
|
|
@@ -155,10 +165,9 @@ The `attach` and `load-context` responses already include `attachments` and `obj
|
|
|
155
165
|
- Do not invent protocol subcommands. Use `ovld protocol help` when unsure.
|
|
156
166
|
- The `summary` in deliver is what the PM reads first, so write it as a narrative, not a command list.
|
|
157
167
|
- Use `write-context` for facts a future agent session should know.
|
|
158
|
-
- If `ovld` reports `OVERLORD_URL` is unreachable, request permission escalation or network access before retrying.
|
|
159
168
|
- If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
|
|
160
169
|
- If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
|
|
161
170
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
162
171
|
- Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
|
|
163
172
|
|
|
164
|
-
<!-- version: 0.
|
|
173
|
+
<!-- version: 0.4.7 -->
|
|
@@ -69,7 +69,7 @@ process.stdin.on('data', async chunk => {
|
|
|
69
69
|
result: {
|
|
70
70
|
protocolVersion: PROTOCOL_VERSION,
|
|
71
71
|
capabilities: { tools: { listChanged: false } },
|
|
72
|
-
serverInfo: { name: 'overlord-cursor', version: '0.1.
|
|
72
|
+
serverInfo: { name: 'overlord-cursor', version: '0.1.2' }
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
continue;
|
|
@@ -81,21 +81,26 @@ process.stdin.on('data', async chunk => {
|
|
|
81
81
|
result: {
|
|
82
82
|
tools: [
|
|
83
83
|
{
|
|
84
|
-
name: '
|
|
84
|
+
name: 'attach',
|
|
85
85
|
description: 'Attach to an Overlord ticket.',
|
|
86
86
|
inputSchema: { type: 'object', properties: { ticket_id: { type: 'string' } }, required: ['ticket_id'] }
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
name: '
|
|
89
|
+
name: 'update',
|
|
90
90
|
description: 'Post a progress update.',
|
|
91
91
|
inputSchema: {
|
|
92
92
|
type: 'object',
|
|
93
|
-
properties: {
|
|
93
|
+
properties: {
|
|
94
|
+
session_key: { type: 'string' },
|
|
95
|
+
ticket_id: { type: 'string' },
|
|
96
|
+
summary: { type: 'string' },
|
|
97
|
+
phase: { type: 'string' }
|
|
98
|
+
},
|
|
94
99
|
required: ['session_key', 'ticket_id', 'summary']
|
|
95
100
|
}
|
|
96
101
|
},
|
|
97
102
|
{
|
|
98
|
-
name: '
|
|
103
|
+
name: 'deliver',
|
|
99
104
|
description: 'Deliver completed work.',
|
|
100
105
|
inputSchema: {
|
|
101
106
|
type: 'object',
|
|
@@ -111,9 +116,9 @@ process.stdin.on('data', async chunk => {
|
|
|
111
116
|
if (message.method === 'tools/call') {
|
|
112
117
|
const toolName = message.params?.name;
|
|
113
118
|
const args = message.params?.arguments ?? {};
|
|
114
|
-
if (toolName === '
|
|
119
|
+
if (toolName === 'attach') {
|
|
115
120
|
send({ jsonrpc: '2.0', id: message.id, result: await runProtocol('attach', { 'ticket-id': args.ticket_id }) });
|
|
116
|
-
} else if (toolName === '
|
|
121
|
+
} else if (toolName === 'update') {
|
|
117
122
|
send({
|
|
118
123
|
jsonrpc: '2.0',
|
|
119
124
|
id: message.id,
|
|
@@ -121,10 +126,10 @@ process.stdin.on('data', async chunk => {
|
|
|
121
126
|
'session-key': args.session_key,
|
|
122
127
|
'ticket-id': args.ticket_id,
|
|
123
128
|
summary: args.summary,
|
|
124
|
-
phase: 'execute'
|
|
129
|
+
phase: args.phase && String(args.phase).trim() ? String(args.phase).trim() : 'execute'
|
|
125
130
|
})
|
|
126
131
|
});
|
|
127
|
-
} else if (toolName === '
|
|
132
|
+
} else if (toolName === 'deliver') {
|
|
128
133
|
send({
|
|
129
134
|
jsonrpc: '2.0',
|
|
130
135
|
id: message.id,
|
|
@@ -17,17 +17,99 @@ Use this skill whenever Cursor needs to work with Overlord, whether the session
|
|
|
17
17
|
6. If blocked, call `ovld protocol ask` and stop.
|
|
18
18
|
7. Deliver last with `ovld protocol deliver`, including `changeRationales` for each meaningful behavioral file change.
|
|
19
19
|
|
|
20
|
+
### Mode 1 Reference
|
|
21
|
+
|
|
22
|
+
Attach:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
ovld protocol attach --ticket-id $TICKET_ID
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Update:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Supported `--phase` values:
|
|
35
|
+
- `draft`
|
|
36
|
+
- `execute`
|
|
37
|
+
- `review`
|
|
38
|
+
- `deliver`
|
|
39
|
+
- `complete`
|
|
40
|
+
- `blocked`
|
|
41
|
+
- `cancelled`
|
|
42
|
+
|
|
43
|
+
Event types:
|
|
44
|
+
- `update` for standard progress updates
|
|
45
|
+
- `user_follow_up` for human follow-up messages after the initial ticket
|
|
46
|
+
- `alert` for warnings or non-blocking issues
|
|
47
|
+
|
|
48
|
+
Ask when blocked:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
ovld protocol ask --session-key <sessionKey> --ticket-id $TICKET_ID --question "Specific question for the PM."
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Deliver:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ovld protocol deliver --session-key <sessionKey> \
|
|
58
|
+
--ticket-id $TICKET_ID \
|
|
59
|
+
--summary "Narrative: what you did, next steps." \
|
|
60
|
+
--artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \
|
|
61
|
+
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed.
|
|
65
|
+
|
|
20
66
|
## Mode 2: Asked From Chat To Use Overlord
|
|
21
67
|
|
|
22
68
|
1. If the user wants to create tickets (and does not ask to start execution), use `/create` or run `ovld protocol create --agent cursor --objective "..."`.
|
|
23
|
-
|
|
69
|
+
- When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
|
|
70
|
+
- When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
|
|
71
|
+
2. Default to `create` for new tickets. Only use `/prompt` or `ovld protocol prompt --agent cursor --objective "..."` when the user explicitly asks to create and execute immediately.
|
|
24
72
|
3. If the user already has a ticket ID and only wants to inspect it, use `/load` or run `ovld protocol load-context --ticket-id <ticket_id>`.
|
|
25
73
|
4. If the user wants to route the current session onto an existing ticket by ID, use `/connect` or run `ovld protocol connect --ticket-id <ticket_id>`.
|
|
26
|
-
5. If the user wants to
|
|
27
|
-
6. If
|
|
28
|
-
7.
|
|
74
|
+
5. If the user wants to establish a persistent session with a ticket by ID, use `/attach` or run `ovld protocol attach --ticket-id <ticket_id>`.
|
|
75
|
+
6. If the user wants to find a ticket by keyword/status/project/creator/date, run `ovld protocol search-tickets --query "..." --status next-up,execute`.
|
|
76
|
+
7. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
|
|
77
|
+
8. Once you attach to a ticket, switch back to Mode 1 and follow the full ticket lifecycle.
|
|
78
|
+
|
|
79
|
+
## Change Rationales
|
|
80
|
+
|
|
81
|
+
Always include `changeRationales` when delivering. Optionally include them on updates during long-running work.
|
|
82
|
+
|
|
83
|
+
Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact.
|
|
29
84
|
|
|
30
|
-
|
|
85
|
+
```bash
|
|
86
|
+
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
87
|
+
--summary "Recorded rationale details for the latest code changes." --phase execute \
|
|
88
|
+
--change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
93
|
+
--summary "Added retry logic." --phase execute \
|
|
94
|
+
--change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Record only meaningful behavioral changes. Skip formatting-only noise.
|
|
98
|
+
|
|
99
|
+
## Project Discovery And Ticket Creation
|
|
100
|
+
|
|
101
|
+
When creating tickets from within a repository:
|
|
102
|
+
- Prefer `create` by default for draft ticket creation.
|
|
103
|
+
- Use `prompt` only when the user explicitly asks to start execution immediately.
|
|
104
|
+
- Both commands resolve the project from the current working directory; use `--working-directory` to override or `--project-id` to be explicit.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
ovld protocol create --agent cursor --objective "Capture follow-up work from this repository"
|
|
108
|
+
ovld protocol prompt --agent cursor --objective "Implement feature X" --priority medium
|
|
109
|
+
ovld protocol discover-project
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Choosing `--execution-target`
|
|
31
113
|
|
|
32
114
|
Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
|
|
33
115
|
|
|
@@ -36,14 +118,38 @@ Pass `--execution-target agent` or `--execution-target human` (default: `human`)
|
|
|
36
118
|
|
|
37
119
|
When in doubt, ask yourself: *can this be done entirely inside a terminal or browser by an AI without human intervention?* If yes → `agent`. If it requires a human to log in, decide, or act in the real world → `human`.
|
|
38
120
|
|
|
121
|
+
## Context And Artifacts
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
|
|
125
|
+
ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
|
|
126
|
+
ovld protocol attachment-list --session-key <sessionKey> --ticket-id $TICKET_ID
|
|
127
|
+
ovld protocol attachment-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --objective-id <objective-id> --file ./spec.pdf --content-type application/pdf
|
|
128
|
+
ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --attachment-id <attachment-id>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The `attach` and `load-context` responses already include `attachments` and `objectives` arrays — use those for `<attachment-id>` and `<objective-id>` values. Run `attachment-list` mid-session if new files have been uploaded since attach.
|
|
132
|
+
|
|
133
|
+
Objective attachment uploads also expose two-step variants — `attachment-prepare-upload` and `attachment-finalize-upload` — for callers that need a signed URL directly. Prefer `attachment-upload-file` for one-shot uploads.
|
|
134
|
+
|
|
135
|
+
"Artifacts" in `deliver` are the structured records an agent submits at delivery time (next_steps, test_results, migration, decision, note, url) — not user-uploaded files.
|
|
136
|
+
|
|
137
|
+
## Defaults And Notes
|
|
138
|
+
|
|
139
|
+
- The Overlord API requires `agentIdentifier` and `connectionMethod` on attach/connect/prompt, but the CLI defaults them to `cursor`/`cli` (override with `--agent` / `--method`). The MCP tools default to `mcp`.
|
|
140
|
+
- The Cursor local MCP stub exposes `attach`, `update`, and `deliver` only (same names as the hosted Overlord MCP core set).
|
|
141
|
+
- `permission-request` is invoked by the installed permission hook; agents normally do not call it directly.
|
|
142
|
+
- The `record_change_rationales` MCP tool and `ovld protocol record-change-rationales` CLI both write to the same `file_changes` table; pick whichever fits your runtime.
|
|
143
|
+
|
|
39
144
|
## Rules
|
|
40
145
|
|
|
41
146
|
- Always attach first and always deliver last once you are on a ticket.
|
|
42
147
|
- Use `ovld protocol` commands, the plugin commands, and the MCP tool instead of ad hoc scripts.
|
|
43
148
|
- Do not invent protocol subcommands. Use `ovld protocol help` when unsure.
|
|
44
|
-
- If `ovld` reports `OVERLORD_URL` is unreachable, request permission escalation or network access before retrying.
|
|
45
|
-
- If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
|
|
46
149
|
- Include at least one progress update before delivering.
|
|
47
|
-
-
|
|
150
|
+
- The `summary` in deliver is what the PM reads first, so write it as a narrative, not a command list.
|
|
151
|
+
- If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
|
|
152
|
+
- If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
|
|
153
|
+
- Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
|
|
48
154
|
|
|
49
|
-
<!-- version: 0.
|
|
155
|
+
<!-- version: 0.4.7 -->
|
|
@@ -22,9 +22,9 @@ personal marketplace entry at `~/.agents/plugins/marketplace.json`.
|
|
|
22
22
|
|
|
23
23
|
- Project resolution: `discover_project`
|
|
24
24
|
- Ticket search: `search_tickets` — keyword search + status/filter (direct API call)
|
|
25
|
-
- Ticket session flow: `
|
|
26
|
-
- Progress and review flow: `
|
|
27
|
-
- Shared context: `
|
|
25
|
+
- Ticket session flow: `attach`, `connect`, `load_ticket_context`, `prompt`
|
|
26
|
+
- Progress and review flow: `update`, `record_change_rationales`, `ask`, `deliver`
|
|
27
|
+
- Shared context: `read_context`, `write_context`
|
|
28
28
|
- Objective attachments: `list_attachments`, `prepare_attachment_upload`, `finalize_attachment_upload`, `get_attachment_download_url`, `upload_attachment_file`
|
|
29
29
|
|
|
30
30
|
The MCP server shells into the installed `ovld` binary so the plugin stays aligned with the shipped CLI behavior instead of depending on this repository checkout.
|
|
@@ -44,56 +44,67 @@ const tools = [
|
|
|
44
44
|
subcommand: 'discover-project'
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
name: '
|
|
47
|
+
name: 'attach',
|
|
48
48
|
description: 'Attach an agent session to an existing Overlord ticket and return the working context.',
|
|
49
49
|
inputSchema: {
|
|
50
50
|
type: 'object',
|
|
51
51
|
properties: {
|
|
52
|
-
ticket_id: { type: 'string', description: '
|
|
52
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
53
53
|
agent: { type: 'string' },
|
|
54
|
-
model: { type: 'string' },
|
|
55
54
|
method: { type: 'string' },
|
|
56
55
|
external_session_id: { type: ['string', 'null'] },
|
|
57
|
-
metadata: {
|
|
56
|
+
metadata: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
description: 'Optional extra metadata merged into the attach request (same as --metadata-json on the CLI).'
|
|
59
|
+
}
|
|
58
60
|
},
|
|
59
61
|
required: ['ticket_id']
|
|
60
62
|
},
|
|
61
63
|
toCliFlags: args => ({
|
|
62
64
|
'ticket-id': args.ticket_id,
|
|
63
65
|
agent: args.agent,
|
|
64
|
-
model: args.model,
|
|
65
66
|
method: args.method,
|
|
66
67
|
'external-session-id': args.external_session_id,
|
|
67
|
-
|
|
68
|
+
...(args.metadata && typeof args.metadata === 'object' && !Array.isArray(args.metadata)
|
|
69
|
+
? { 'metadata-json': args.metadata }
|
|
70
|
+
: {})
|
|
68
71
|
}),
|
|
69
72
|
subcommand: 'attach'
|
|
70
73
|
},
|
|
71
74
|
{
|
|
72
|
-
name: '
|
|
75
|
+
name: 'connect',
|
|
73
76
|
description: 'Create a lightweight Overlord session without loading the full ticket context.',
|
|
74
77
|
inputSchema: {
|
|
75
78
|
type: 'object',
|
|
76
79
|
properties: {
|
|
77
|
-
ticket_id: { type: 'string' },
|
|
80
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
78
81
|
agent: { type: 'string' },
|
|
79
|
-
method: { type: 'string' }
|
|
82
|
+
method: { type: 'string' },
|
|
83
|
+
metadata: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
description: 'Optional extra metadata merged into the connect request (same as --metadata-json on the CLI).'
|
|
86
|
+
}
|
|
80
87
|
},
|
|
81
88
|
required: ['ticket_id']
|
|
82
89
|
},
|
|
83
90
|
toCliFlags: args => ({
|
|
84
91
|
'ticket-id': args.ticket_id,
|
|
85
92
|
agent: args.agent,
|
|
86
|
-
method: args.method
|
|
93
|
+
method: args.method,
|
|
94
|
+
...(args.metadata && typeof args.metadata === 'object' && !Array.isArray(args.metadata)
|
|
95
|
+
? { 'metadata-json': args.metadata }
|
|
96
|
+
: {})
|
|
87
97
|
}),
|
|
88
98
|
subcommand: 'connect'
|
|
89
99
|
},
|
|
90
100
|
{
|
|
91
101
|
name: 'load_ticket_context',
|
|
92
|
-
description:
|
|
102
|
+
description:
|
|
103
|
+
'Fetch Overlord ticket context without creating a session (maps to ovld protocol load-context; not the same as read_context).',
|
|
93
104
|
inputSchema: {
|
|
94
105
|
type: 'object',
|
|
95
106
|
properties: {
|
|
96
|
-
ticket_id: { type: 'string' }
|
|
107
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' }
|
|
97
108
|
},
|
|
98
109
|
required: ['ticket_id']
|
|
99
110
|
},
|
|
@@ -103,8 +114,8 @@ const tools = [
|
|
|
103
114
|
subcommand: 'load-context'
|
|
104
115
|
},
|
|
105
116
|
{
|
|
106
|
-
name: '
|
|
107
|
-
description: 'Create a
|
|
117
|
+
name: 'prompt',
|
|
118
|
+
description: 'Create a ticket and attach to it immediately (ovld protocol prompt).',
|
|
108
119
|
inputSchema: {
|
|
109
120
|
type: 'object',
|
|
110
121
|
properties: {
|
|
@@ -113,6 +124,7 @@ const tools = [
|
|
|
113
124
|
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
114
125
|
project_id: { type: 'string' },
|
|
115
126
|
working_directory: { type: 'string' },
|
|
127
|
+
personal: { type: 'boolean', description: 'Create without assigning a project (private ticket).' },
|
|
116
128
|
acceptance_criteria: { type: 'string' },
|
|
117
129
|
available_tools: { type: 'string' },
|
|
118
130
|
execution_target: { type: 'string', enum: ['agent', 'human'] },
|
|
@@ -120,8 +132,6 @@ const tools = [
|
|
|
120
132
|
parent_session_key: { type: 'string' },
|
|
121
133
|
parent_ticket_id: { type: 'string' },
|
|
122
134
|
agent: { type: 'string' },
|
|
123
|
-
model: { type: 'string' },
|
|
124
|
-
metadata: { type: 'object' },
|
|
125
135
|
method: { type: 'string' }
|
|
126
136
|
},
|
|
127
137
|
required: ['objective']
|
|
@@ -132,6 +142,7 @@ const tools = [
|
|
|
132
142
|
priority: args.priority,
|
|
133
143
|
'project-id': args.project_id,
|
|
134
144
|
'working-directory': args.working_directory,
|
|
145
|
+
personal: args.personal === true ? true : undefined,
|
|
135
146
|
'acceptance-criteria': args.acceptance_criteria,
|
|
136
147
|
'available-tools': args.available_tools,
|
|
137
148
|
'execution-target': args.execution_target,
|
|
@@ -139,20 +150,18 @@ const tools = [
|
|
|
139
150
|
'parent-session-key': args.parent_session_key,
|
|
140
151
|
'parent-ticket-id': args.parent_ticket_id,
|
|
141
152
|
agent: args.agent,
|
|
142
|
-
model: args.model,
|
|
143
|
-
'metadata-json': args.metadata,
|
|
144
153
|
method: args.method
|
|
145
154
|
}),
|
|
146
|
-
subcommand: '
|
|
155
|
+
subcommand: 'prompt'
|
|
147
156
|
},
|
|
148
157
|
{
|
|
149
|
-
name: '
|
|
158
|
+
name: 'update',
|
|
150
159
|
description: 'Post an Overlord progress update or activity event.',
|
|
151
160
|
inputSchema: {
|
|
152
161
|
type: 'object',
|
|
153
162
|
properties: {
|
|
154
163
|
session_key: { type: 'string' },
|
|
155
|
-
ticket_id: { type: 'string' },
|
|
164
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
156
165
|
summary: { type: 'string' },
|
|
157
166
|
phase: { type: 'string', enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled'] },
|
|
158
167
|
event_type: { type: 'string', enum: ['update', 'user_follow_up', 'alert'] },
|
|
@@ -183,7 +192,7 @@ const tools = [
|
|
|
183
192
|
type: 'object',
|
|
184
193
|
properties: {
|
|
185
194
|
session_key: { type: 'string' },
|
|
186
|
-
ticket_id: { type: 'string' },
|
|
195
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
187
196
|
summary: { type: 'string' },
|
|
188
197
|
phase: { type: 'string', enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled'] },
|
|
189
198
|
change_rationales: { type: 'array' }
|
|
@@ -200,13 +209,13 @@ const tools = [
|
|
|
200
209
|
subcommand: 'record-change-rationales'
|
|
201
210
|
},
|
|
202
211
|
{
|
|
203
|
-
name: '
|
|
212
|
+
name: 'ask',
|
|
204
213
|
description: 'Send a blocking question to the human reviewer or PM.',
|
|
205
214
|
inputSchema: {
|
|
206
215
|
type: 'object',
|
|
207
216
|
properties: {
|
|
208
217
|
session_key: { type: 'string' },
|
|
209
|
-
ticket_id: { type: 'string' },
|
|
218
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
210
219
|
question: { type: 'string' },
|
|
211
220
|
phase: { type: 'string', enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled'] },
|
|
212
221
|
payload: { type: 'object' }
|
|
@@ -223,13 +232,13 @@ const tools = [
|
|
|
223
232
|
subcommand: 'ask'
|
|
224
233
|
},
|
|
225
234
|
{
|
|
226
|
-
name: '
|
|
235
|
+
name: 'read_context',
|
|
227
236
|
description: 'Read persistent shared context entries for a ticket.',
|
|
228
237
|
inputSchema: {
|
|
229
238
|
type: 'object',
|
|
230
239
|
properties: {
|
|
231
240
|
session_key: { type: 'string' },
|
|
232
|
-
ticket_id: { type: 'string' },
|
|
241
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
233
242
|
query: { type: 'string' },
|
|
234
243
|
limit: { type: 'number' }
|
|
235
244
|
},
|
|
@@ -244,13 +253,13 @@ const tools = [
|
|
|
244
253
|
subcommand: 'read-context'
|
|
245
254
|
},
|
|
246
255
|
{
|
|
247
|
-
name: '
|
|
256
|
+
name: 'write_context',
|
|
248
257
|
description: 'Write a persistent shared context entry for future Overlord sessions.',
|
|
249
258
|
inputSchema: {
|
|
250
259
|
type: 'object',
|
|
251
260
|
properties: {
|
|
252
261
|
session_key: { type: 'string' },
|
|
253
|
-
ticket_id: { type: 'string' },
|
|
262
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
254
263
|
key: { type: 'string' },
|
|
255
264
|
value: {},
|
|
256
265
|
tags: { type: 'array', items: { type: 'string' } }
|
|
@@ -267,17 +276,28 @@ const tools = [
|
|
|
267
276
|
subcommand: 'write-context'
|
|
268
277
|
},
|
|
269
278
|
{
|
|
270
|
-
name: '
|
|
279
|
+
name: 'deliver',
|
|
271
280
|
description:
|
|
272
|
-
'Deliver final work back into Overlord with summary, artifacts, and change rationales. Large payloads are streamed to the CLI through stdin, so this tool does not create delivery scratch files.',
|
|
281
|
+
'Deliver final work back into Overlord with summary, optional artifacts, and optional change rationales (matches POST /api/protocol/deliver). Large payloads are streamed to the CLI through stdin, so this tool does not create delivery scratch files.',
|
|
273
282
|
inputSchema: {
|
|
274
283
|
type: 'object',
|
|
275
284
|
properties: {
|
|
276
285
|
session_key: { type: 'string' },
|
|
277
|
-
ticket_id: { type: 'string' },
|
|
286
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
278
287
|
summary: { type: 'string' },
|
|
279
288
|
artifacts: { type: 'array' },
|
|
280
289
|
change_rationales: { type: 'array' },
|
|
290
|
+
snapshot: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
description:
|
|
293
|
+
'Optional snapshot metadata (jj/git-worktree, workspace, jj ids). Merged with OVERLORD_SNAPSHOT_JSON when both are set.'
|
|
294
|
+
},
|
|
295
|
+
checkpoint: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
description:
|
|
298
|
+
'Optional checkpoint metadata. The local CLI creates this automatically for JJ/Git workspaces during deliver.'
|
|
299
|
+
},
|
|
300
|
+
skip_checkpoint: { type: 'boolean' },
|
|
281
301
|
skip_file_change_check: { type: 'boolean' }
|
|
282
302
|
},
|
|
283
303
|
required: ['session_key', 'ticket_id', 'summary']
|
|
@@ -286,13 +306,17 @@ const tools = [
|
|
|
286
306
|
'session-key': args.session_key,
|
|
287
307
|
'ticket-id': args.ticket_id,
|
|
288
308
|
'payload-file': '-',
|
|
309
|
+
'skip-checkpoint': args.skip_checkpoint,
|
|
289
310
|
'skip-file-change-check': args.skip_file_change_check
|
|
290
311
|
}),
|
|
291
|
-
toCliStdin: args =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
toCliStdin: args =>
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
summary: args.summary,
|
|
315
|
+
...(Array.isArray(args.artifacts) ? { artifacts: args.artifacts } : {}),
|
|
316
|
+
...(Array.isArray(args.change_rationales) ? { changeRationales: args.change_rationales } : {}),
|
|
317
|
+
...(args.snapshot && typeof args.snapshot === 'object' ? { snapshot: args.snapshot } : {}),
|
|
318
|
+
...(args.checkpoint && typeof args.checkpoint === 'object' ? { checkpoint: args.checkpoint } : {})
|
|
319
|
+
}),
|
|
296
320
|
subcommand: 'deliver'
|
|
297
321
|
},
|
|
298
322
|
{
|
|
@@ -303,7 +327,7 @@ const tools = [
|
|
|
303
327
|
type: 'object',
|
|
304
328
|
properties: {
|
|
305
329
|
session_key: { type: 'string' },
|
|
306
|
-
ticket_id: { type: 'string' },
|
|
330
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
307
331
|
objective_id: { type: 'string' }
|
|
308
332
|
},
|
|
309
333
|
required: ['session_key', 'ticket_id']
|
|
@@ -322,7 +346,7 @@ const tools = [
|
|
|
322
346
|
type: 'object',
|
|
323
347
|
properties: {
|
|
324
348
|
session_key: { type: 'string' },
|
|
325
|
-
ticket_id: { type: 'string' },
|
|
349
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
326
350
|
objective_id: { type: 'string' },
|
|
327
351
|
file_name: { type: 'string' },
|
|
328
352
|
label: { type: 'string' },
|
|
@@ -351,7 +375,7 @@ const tools = [
|
|
|
351
375
|
type: 'object',
|
|
352
376
|
properties: {
|
|
353
377
|
session_key: { type: 'string' },
|
|
354
|
-
ticket_id: { type: 'string' },
|
|
378
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
355
379
|
objective_id: { type: 'string' },
|
|
356
380
|
storage_path: { type: 'string' },
|
|
357
381
|
label: { type: 'string' },
|
|
@@ -380,7 +404,7 @@ const tools = [
|
|
|
380
404
|
type: 'object',
|
|
381
405
|
properties: {
|
|
382
406
|
session_key: { type: 'string' },
|
|
383
|
-
ticket_id: { type: 'string' },
|
|
407
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
384
408
|
objective_id: { type: 'string' },
|
|
385
409
|
attachment_id: { type: 'string' },
|
|
386
410
|
storage_path: { type: 'string' },
|
|
@@ -405,7 +429,7 @@ const tools = [
|
|
|
405
429
|
type: 'object',
|
|
406
430
|
properties: {
|
|
407
431
|
session_key: { type: 'string' },
|
|
408
|
-
ticket_id: { type: 'string' },
|
|
432
|
+
ticket_id: { type: 'string', description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.' },
|
|
409
433
|
objective_id: { type: 'string' },
|
|
410
434
|
file: { type: 'string' },
|
|
411
435
|
file_name: { type: 'string' },
|
|
@@ -453,7 +477,7 @@ const searchTicketsTool = {
|
|
|
453
477
|
},
|
|
454
478
|
limit: {
|
|
455
479
|
type: 'number',
|
|
456
|
-
description: 'Maximum number of results to return (1–
|
|
480
|
+
description: 'Maximum number of results to return (1–50, default 8).'
|
|
457
481
|
}
|
|
458
482
|
}
|
|
459
483
|
},
|
|
@@ -466,10 +490,9 @@ const searchTicketsTool = {
|
|
|
466
490
|
subcommand: 'search-tickets'
|
|
467
491
|
};
|
|
468
492
|
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
]);
|
|
493
|
+
const allListedTools = [...tools, searchTicketsTool];
|
|
494
|
+
|
|
495
|
+
const toolMap = new Map(allListedTools.map(tool => [tool.name, tool]));
|
|
473
496
|
let buffer = Buffer.alloc(0);
|
|
474
497
|
|
|
475
498
|
function serializeMessage(message) {
|
|
@@ -632,10 +655,10 @@ async function handleRequest(message) {
|
|
|
632
655
|
},
|
|
633
656
|
serverInfo: {
|
|
634
657
|
name: 'overlord',
|
|
635
|
-
version: '0.
|
|
658
|
+
version: '0.1.4'
|
|
636
659
|
},
|
|
637
660
|
instructions:
|
|
638
|
-
'Use these tools to drive Overlord ticket workflows through the installed ovld CLI.
|
|
661
|
+
'Use these tools to drive Overlord ticket workflows through the installed ovld CLI. Tool names match the hosted Overlord MCP server (attach, update, deliver, …). Most operations expect a session key from attach or connect.'
|
|
639
662
|
});
|
|
640
663
|
return;
|
|
641
664
|
}
|
|
@@ -647,7 +670,7 @@ async function handleRequest(message) {
|
|
|
647
670
|
|
|
648
671
|
if (method === 'tools/list') {
|
|
649
672
|
success(id, {
|
|
650
|
-
tools:
|
|
673
|
+
tools: allListedTools.map(tool => ({
|
|
651
674
|
name: tool.name,
|
|
652
675
|
description: tool.description,
|
|
653
676
|
inputSchema: tool.inputSchema
|
|
@@ -63,18 +63,45 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
63
63
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
66
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
67
67
|
|
|
68
68
|
## Mode 2: Asked From Chat To Use Overlord
|
|
69
69
|
|
|
70
70
|
1. If the user wants to create tickets (and does not ask to start execution), run `ovld protocol create --agent codex --objective "..."`.
|
|
71
|
-
|
|
71
|
+
- When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
|
|
72
|
+
- When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
|
|
73
|
+
2. Default to `create` for new tickets. Only use `ovld protocol prompt --agent codex --objective "..."` when the user explicitly asks to create and execute immediately.
|
|
74
|
+
`prompt` creates the ticket in `execute` status and attaches immediately.
|
|
72
75
|
3. If the user wants to inspect an existing ticket without starting work, use `ovld protocol load-context --ticket-id <ticket_id>`.
|
|
73
|
-
4. If the user wants to work an existing ticket, attach with `ovld protocol attach --ticket-id <ticket_id>` and then switch to Mode 1.
|
|
74
|
-
5. If the user wants to find existing tickets by keyword or
|
|
75
|
-
6. If you need to understand project routing before
|
|
76
|
+
4. If the user wants to work an existing ticket, attach with `ovld protocol attach --ticket-id <ticket_id>` and then switch to Mode 1. Use `ovld protocol connect --ticket-id <ticket_id>` instead when you only need a session key without the full ticket payload.
|
|
77
|
+
5. If the user wants to find existing tickets by keyword, status, project, creator, or update window, run `ovld protocol search-tickets --query "..." --status next-up,execute --limit 10`. The MCP `search_tickets` tool exposes the same filters.
|
|
78
|
+
6. If you need to understand project routing before prompting, use `ovld protocol discover-project`.
|
|
76
79
|
7. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
|
|
77
80
|
|
|
81
|
+
## Project Discovery And Ticket Creation
|
|
82
|
+
|
|
83
|
+
When creating tickets from within a repository:
|
|
84
|
+
- Prefer `create` by default for draft ticket creation.
|
|
85
|
+
- Use `prompt` only when the user explicitly asks to start execution immediately.
|
|
86
|
+
- Both commands can resolve the project from the current working directory; use `--working-directory` to override.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ovld protocol create --agent codex --objective "Capture follow-up work from this repository"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ovld protocol prompt --agent codex --objective "Implement feature X" --priority medium
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Choosing `--execution-target`
|
|
97
|
+
|
|
98
|
+
Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
|
|
99
|
+
|
|
100
|
+
- **`agent`** — any task an AI agent can complete in a computer environment: coding, internet research, document editing, data analysis, automated testing, etc.
|
|
101
|
+
- **`human`** — any task requiring human presence or judgment: setting credentials or tokens in a third-party UI (e.g. Vercel, AWS), sending physical mail, making a product or business decision, physical-world actions.
|
|
102
|
+
|
|
103
|
+
When in doubt, ask yourself: *can this be done entirely inside a terminal or browser by an AI without human intervention?* If yes → `agent`. If it requires a human to log in, decide, or act in the real world → `human`.
|
|
104
|
+
|
|
78
105
|
## Change Rationales
|
|
79
106
|
|
|
80
107
|
Always include `changeRationales` when delivering. Optionally include them on updates during long-running work.
|
|
@@ -109,38 +136,23 @@ ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TI
|
|
|
109
136
|
|
|
110
137
|
The `attach` and `load-context` responses already include `attachments` and `objectives` arrays — use those for `<attachment-id>` and `<objective-id>` values. Run `attachment-list` mid-session if new files have been uploaded since attach.
|
|
111
138
|
|
|
112
|
-
##
|
|
113
|
-
|
|
114
|
-
When creating tickets from within a repository:
|
|
115
|
-
- Prefer `create` by default for draft ticket creation.
|
|
116
|
-
- Use `spawn` only when the user explicitly asks to start execution immediately.
|
|
117
|
-
- Both commands can resolve the project from the current working directory; use `--working-directory` to override.
|
|
139
|
+
## Defaults And Notes
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Choosing `--execution-target`
|
|
128
|
-
|
|
129
|
-
Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
|
|
130
|
-
|
|
131
|
-
- **`agent`** — any task an AI agent can complete in a computer environment: coding, internet research, document editing, data analysis, automated testing, etc.
|
|
132
|
-
- **`human`** — any task requiring human presence or judgment: setting credentials or tokens in a third-party UI (e.g. Vercel, AWS), sending physical mail, making a product or business decision, physical-world actions.
|
|
133
|
-
|
|
134
|
-
When in doubt, ask yourself: *can this be done entirely inside a terminal or browser by an AI without human intervention?* If yes → `agent`. If it requires a human to log in, decide, or act in the real world → `human`.
|
|
141
|
+
- API requires `agentIdentifier` and `connectionMethod` on attach/connect/prompt. The CLI defaults them to `codex`/`cli`; the hosted MCP defaults to `mcp`. Override with `--agent` / `--method` when calling from a different runtime.
|
|
142
|
+
- Hosted MCP and the local `overlord-mcp.mjs` shim use the same **tool names** (\`attach\`, \`update\`, \`deliver\`, …). The shim uses **snake_case** JSON keys (\`ticket_id\`, \`session_key\`) that map to CLI flags; hosted MCP uses **camelCase** (\`ticketId\`, \`sessionKey\`) matching REST bodies.
|
|
143
|
+
- `permission-request` is invoked by the local Codex plugin's permission rules; agents do not normally call it directly.
|
|
144
|
+
- `record_change_rationales` (MCP) and `ovld protocol record-change-rationales` (CLI) both write to the `file_changes` table; the dedicated route is `POST /api/protocol/record-change-rationales`.
|
|
145
|
+
- Objective attachment MCP tools use `<verb>_<noun>` names — `list_attachments`, `prepare_attachment_upload`, `finalize_attachment_upload`, `get_attachment_download_url`, `upload_attachment_file`. CLI commands use `attachment-*` and require `--objective-id` for upload/finalize.
|
|
146
|
+
- "Artifacts" in `deliver` are the structured records an agent submits at delivery time (next_steps, test_results, migration, decision, note, url) — not user-uploaded files.
|
|
135
147
|
|
|
136
148
|
## Rules
|
|
137
149
|
|
|
138
150
|
- The authoritative lifecycle is the `ovld protocol` CLI once you are on a ticket.
|
|
139
151
|
- Always attach first and always deliver last once you are working a ticket.
|
|
140
|
-
- Prefer the installed `ovld` CLI and the plugin's MCP tools instead of ad hoc repo scripts.
|
|
152
|
+
- Prefer the installed `ovld protocol` CLI and the plugin's MCP tools instead of ad hoc repo scripts.
|
|
141
153
|
- Do not create or rely on a local Codex `AGENTS.md` bundle for Overlord.
|
|
142
|
-
- If `ovld` reports `OVERLORD_URL` is unreachable, request permission escalation or network access before retrying.
|
|
143
154
|
- When the ticket was launched by Overlord, the ticket prompt remains authoritative for the specific task objective and ticket-level constraints.
|
|
144
155
|
- If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
|
|
156
|
+
- If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
|
|
145
157
|
|
|
146
|
-
<!-- version: 0.
|
|
158
|
+
<!-- version: 0.4.7 -->
|