overlord-cli 0.2606011152.0 → 0.2606021051.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/index.mjs +7 -0
- package/bin/_cli/onboard.mjs +245 -0
- package/bin/_cli/protocol.mjs +12 -0
- package/package.json +1 -1
- package/plugins/antigravity/scripts/overlord-mcp.mjs +6 -0
- package/plugins/antigravity/scripts/user-prompt-submit-hook.sh +23 -1
- package/plugins/claude/scripts/user-prompt-submit-hook.sh +23 -1
- package/plugins/claude/skills/overlord-ticket/reference/cli.md +1 -1
- package/plugins/claude/skills/overlord-ticket/reference/devices.md +1 -1
- package/plugins/cursor/hooks/overlord-user-prompt-submit.sh +23 -1
- package/plugins/cursor/skills/overlord-ticket/reference/cli.md +1 -1
- package/plugins/cursor/skills/overlord-ticket/reference/devices.md +1 -1
- package/plugins/overlord/scripts/overlord-mcp.mjs +9 -1
- package/plugins/overlord/scripts/user-prompt-submit-hook.sh +34 -1
- package/plugins/overlord/skills/overlord-ticket/reference/cli.md +1 -1
- package/plugins/overlord/skills/overlord-ticket/reference/devices.md +1 -1
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
141
141
|
- `record-change-rationales` - persist structured change rationales without a normal progress update
|
|
142
142
|
- `ask` - post a blocking question and move the ticket to review
|
|
143
143
|
- `permission-request` - notify Overlord that the agent is requesting tool permission
|
|
144
|
-
- `hook-event` - record lifecycle hook events such as `UserPromptSubmit` / Cursor `beforeSubmitPrompt` without a session key
|
|
144
|
+
- `hook-event` - record lifecycle hook events such as `UserPromptSubmit` / Cursor `beforeSubmitPrompt` without a session key; optionally persist a native resume/session id onto the active Overlord session
|
|
145
145
|
- `read-context` - read shared persistent context for this ticket
|
|
146
146
|
- `write-context` - write shared persistent context for future sessions
|
|
147
147
|
- `deliver` - send artifacts/rationales and move the ticket to review
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -34,6 +34,7 @@ Usage:
|
|
|
34
34
|
${primaryCommand} create "<objective>" Create a ticket with numbered project selection; supports --agent/--model/--delegate
|
|
35
35
|
${primaryCommand} prompt "<objective>" Create a ticket, then launch an agent on it
|
|
36
36
|
${primaryCommand} add-cwd Register the current directory as a project resource (interactive)
|
|
37
|
+
${primaryCommand} onboard Create an account, project, cwd link, and onboarding ticket
|
|
37
38
|
${primaryCommand} auth <subcommand> Login, logout, repair, or check auth status
|
|
38
39
|
${primaryCommand} tickets <subcommand> Create or list tickets
|
|
39
40
|
${primaryCommand} ticket <subcommand> Work with a single ticket
|
|
@@ -117,6 +118,12 @@ export async function runCli({ primaryCommand }) {
|
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
if (command === 'onboard') {
|
|
122
|
+
const { runOnboardCommand } = await import('./onboard.mjs');
|
|
123
|
+
await runOnboardCommand(rest);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
// Auth group
|
|
121
128
|
if (command === 'auth') {
|
|
122
129
|
await runAuthCommand(rest[0], rest.slice(1));
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
8
|
+
|
|
9
|
+
import { authLoginViaDeviceFlow, resolveLoginPlatformUrl } from './auth.mjs';
|
|
10
|
+
import {
|
|
11
|
+
buildAuthHeaders,
|
|
12
|
+
loadCredentials,
|
|
13
|
+
loadRuntime,
|
|
14
|
+
saveCredentials
|
|
15
|
+
} from './credentials.mjs';
|
|
16
|
+
import { upsertLocalOverlordConfig } from './local-config.mjs';
|
|
17
|
+
import { readOrCreateDeviceFingerprint } from './runner.mjs';
|
|
18
|
+
|
|
19
|
+
function parseFlags(args) {
|
|
20
|
+
const flags = {};
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
if (!arg.startsWith('--')) continue;
|
|
24
|
+
|
|
25
|
+
const eqIdx = arg.indexOf('=');
|
|
26
|
+
if (eqIdx !== -1) {
|
|
27
|
+
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const key = arg.slice(2);
|
|
32
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
33
|
+
flags[key] = args[i + 1];
|
|
34
|
+
i++;
|
|
35
|
+
} else {
|
|
36
|
+
flags[key] = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return flags;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function openBrowser(url) {
|
|
43
|
+
try {
|
|
44
|
+
if (process.platform === 'darwin') execFileSync('open', [url]);
|
|
45
|
+
else if (process.platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url]);
|
|
46
|
+
else execFileSync('xdg-open', [url]);
|
|
47
|
+
} catch {
|
|
48
|
+
// Best effort. The URL is also printed.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function trimFlag(flags, name) {
|
|
53
|
+
const value = flags[name];
|
|
54
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function promptForValue(question, defaultValue = '') {
|
|
58
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
59
|
+
return defaultValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rl = readline.createInterface({ input, output });
|
|
63
|
+
try {
|
|
64
|
+
return await new Promise((resolve, reject) => {
|
|
65
|
+
const handleError = error => {
|
|
66
|
+
rl.off('error', handleError);
|
|
67
|
+
reject(error);
|
|
68
|
+
};
|
|
69
|
+
rl.once('error', handleError);
|
|
70
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
71
|
+
rl.question(`${question}${suffix}: `, answer => {
|
|
72
|
+
rl.off('error', handleError);
|
|
73
|
+
const trimmed = answer.trim();
|
|
74
|
+
resolve(trimmed || defaultValue);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
rl.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function promptForYes(question, defaultYes = true) {
|
|
83
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
84
|
+
return defaultYes;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const rl = readline.createInterface({ input, output });
|
|
88
|
+
try {
|
|
89
|
+
return await new Promise((resolve, reject) => {
|
|
90
|
+
const handleError = error => {
|
|
91
|
+
rl.off('error', handleError);
|
|
92
|
+
reject(error);
|
|
93
|
+
};
|
|
94
|
+
rl.once('error', handleError);
|
|
95
|
+
const suffix = defaultYes ? 'Y/n' : 'y/N';
|
|
96
|
+
rl.question(`${question} (${suffix}): `, answer => {
|
|
97
|
+
rl.off('error', handleError);
|
|
98
|
+
const normalized = answer.trim().toLowerCase();
|
|
99
|
+
if (!normalized) resolve(defaultYes);
|
|
100
|
+
else resolve(normalized === 'y' || normalized === 'yes');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
} finally {
|
|
104
|
+
rl.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildSignupUrl(platformUrl, verificationUri, name) {
|
|
109
|
+
const verificationUrl = new URL(verificationUri);
|
|
110
|
+
const signupUrl = new URL('/signup', platformUrl);
|
|
111
|
+
signupUrl.searchParams.set('next', `${verificationUrl.pathname}${verificationUrl.search}`);
|
|
112
|
+
if (name) signupUrl.searchParams.set('name', name);
|
|
113
|
+
return signupUrl.toString();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function completeCliOnboarding(platformUrl, localSecret, credentials, payload) {
|
|
117
|
+
const res = await fetch(`${platformUrl}/api/auth/cli-onboarding`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
...buildAuthHeaders(credentials.access_token, localSecret),
|
|
121
|
+
'Content-Type': 'application/json'
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify(payload)
|
|
124
|
+
});
|
|
125
|
+
const data = await res.json().catch(() => ({}));
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Onboarding setup failed (${res.status}): ${data.error ?? JSON.stringify(data)}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildWebProjectUrl(platformUrl, result) {
|
|
135
|
+
const projectId = result?.project?.id;
|
|
136
|
+
if (!projectId) return `${platformUrl}/u`;
|
|
137
|
+
return `${platformUrl}/projects/${projectId}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function runOnboardCommand(args) {
|
|
141
|
+
const flags = parseFlags(args);
|
|
142
|
+
|
|
143
|
+
if (flags.help === true || flags.h === true || args[0] === 'help') {
|
|
144
|
+
console.log(`Usage: ovld onboard [--name <name>] [--organization-name <name>] [--project-name <name>] [--directory <path>] [--yes] [--no-desktop]
|
|
145
|
+
|
|
146
|
+
Creates an Overlord account setup from the terminal:
|
|
147
|
+
1. collects your name, organization, project, and repository directory
|
|
148
|
+
2. opens browser signup/login and authorizes the CLI
|
|
149
|
+
3. creates the organization/project, links this directory, and creates the first onboarding ticket
|
|
150
|
+
4. opens the Desktop download page, or the web project when --no-desktop is used
|
|
151
|
+
|
|
152
|
+
Run this from the repository you want Overlord agents to work in.`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const directoryPath = path.resolve(trimFlag(flags, 'directory') || process.cwd());
|
|
157
|
+
const directoryName = path.basename(directoryPath) || 'My project';
|
|
158
|
+
const inferredName = os.userInfo().username || 'User';
|
|
159
|
+
const name = trimFlag(flags, 'name') || (await promptForValue('Your name', inferredName));
|
|
160
|
+
const organizationName =
|
|
161
|
+
trimFlag(flags, 'organization-name') || (await promptForValue('Organization name', name));
|
|
162
|
+
const projectName =
|
|
163
|
+
trimFlag(flags, 'project-name') || (await promptForValue('Project name', directoryName));
|
|
164
|
+
|
|
165
|
+
if (!name || !organizationName || !projectName) {
|
|
166
|
+
throw new Error('Name, organization name, and project name are required.');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const storedCredentials = loadCredentials();
|
|
170
|
+
const platformUrl = resolveLoginPlatformUrl(null, storedCredentials?.platform_url ?? null);
|
|
171
|
+
const runtime = loadRuntime(platformUrl);
|
|
172
|
+
const localSecret = runtime?.local_secret ?? process.env.OVERLORD_LOCAL_SECRET ?? '';
|
|
173
|
+
|
|
174
|
+
console.log('\nStarting Overlord onboarding in your browser.');
|
|
175
|
+
console.log('Create an account or sign in, then approve the CLI authorization request.\n');
|
|
176
|
+
|
|
177
|
+
const credentials = await authLoginViaDeviceFlow(platformUrl, localSecret, {
|
|
178
|
+
browserOpener: verificationUri => {
|
|
179
|
+
const signupUrl = buildSignupUrl(platformUrl, verificationUri, name);
|
|
180
|
+
console.log(` Signup URL: ${signupUrl}`);
|
|
181
|
+
openBrowser(signupUrl);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const resolvedPlatformUrl = credentials.platform_url ?? platformUrl;
|
|
186
|
+
const deviceFingerprint = readOrCreateDeviceFingerprint(flags);
|
|
187
|
+
const result = await completeCliOnboarding(resolvedPlatformUrl, localSecret, credentials, {
|
|
188
|
+
name,
|
|
189
|
+
organizationName,
|
|
190
|
+
projectName,
|
|
191
|
+
directoryPath,
|
|
192
|
+
deviceFingerprint,
|
|
193
|
+
deviceHostname: os.hostname(),
|
|
194
|
+
devicePlatform: process.platform
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
saveCredentials({
|
|
198
|
+
access_token: credentials.access_token,
|
|
199
|
+
access_token_expires_at: credentials.access_token_expires_at ?? undefined,
|
|
200
|
+
refresh_token: credentials.refresh_token,
|
|
201
|
+
organization_id: result.organization.organizationId,
|
|
202
|
+
platform_url: resolvedPlatformUrl
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const localConfig = await upsertLocalOverlordConfig({
|
|
207
|
+
directoryPath,
|
|
208
|
+
project: { id: result.project.id, name: result.project.name }
|
|
209
|
+
});
|
|
210
|
+
console.log(`\nWrote ${localConfig.filePath} (${localConfig.action}).`);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.log(
|
|
213
|
+
`\nWarning: could not update .overlord/project.json: ${error instanceof Error ? error.message : error}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('\nOverlord setup complete.');
|
|
218
|
+
console.log(` Organization: ${result.organization.organizationName}`);
|
|
219
|
+
console.log(` Project: ${result.project.name}`);
|
|
220
|
+
console.log(` Directory: ${directoryPath}`);
|
|
221
|
+
console.log(` Onboarding ticket: ${result.ticket.reference ?? result.ticket.id}`);
|
|
222
|
+
|
|
223
|
+
const skipDesktop = flags['no-desktop'] === true || flags.desktop === 'false';
|
|
224
|
+
const autoYes = flags.yes === true || flags.y === true;
|
|
225
|
+
const shouldOpenDesktop =
|
|
226
|
+
!skipDesktop &&
|
|
227
|
+
(autoYes ||
|
|
228
|
+
(await promptForYes(
|
|
229
|
+
'\nRecommended: download Overlord Desktop for terminal launches and file-change tracking. Open downloads now?',
|
|
230
|
+
true
|
|
231
|
+
)));
|
|
232
|
+
|
|
233
|
+
if (shouldOpenDesktop) {
|
|
234
|
+
const downloadsUrl = `${resolvedPlatformUrl}/downloads`;
|
|
235
|
+
console.log(`Opening ${downloadsUrl}`);
|
|
236
|
+
openBrowser(downloadsUrl);
|
|
237
|
+
} else {
|
|
238
|
+
const webUrl = buildWebProjectUrl(resolvedPlatformUrl, result);
|
|
239
|
+
console.log(`Opening ${webUrl}`);
|
|
240
|
+
openBrowser(webUrl);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log('\nNext command from this repo:');
|
|
244
|
+
console.log(' ovld prompt --objectives-json \'[{"objective":"Describe the work you want done"}]\'');
|
|
245
|
+
}
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -1296,6 +1296,12 @@ async function protocolHookEvent(args) {
|
|
|
1296
1296
|
? { turnIndex: parseInt(String(flags['turn-index']), 10) }
|
|
1297
1297
|
: {}),
|
|
1298
1298
|
...(flags['follow-up-intent'] ? { followUpIntent: String(flags['follow-up-intent']) } : {}),
|
|
1299
|
+
...(() => {
|
|
1300
|
+
if (flags['external-session-id'] === undefined) return {};
|
|
1301
|
+
const raw = String(flags['external-session-id']).trim();
|
|
1302
|
+
if (raw === '') return {};
|
|
1303
|
+
return { externalSessionId: raw.toLowerCase() === 'null' ? null : raw };
|
|
1304
|
+
})(),
|
|
1299
1305
|
...(sessionKey ? { sessionKey } : {})
|
|
1300
1306
|
};
|
|
1301
1307
|
|
|
@@ -2200,11 +2206,13 @@ async function protocolConnect(args) {
|
|
|
2200
2206
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
2201
2207
|
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
2202
2208
|
const timeoutMs = resolveTimeout(flags);
|
|
2209
|
+
const externalSessionId = resolveExternalSessionId(flags);
|
|
2203
2210
|
|
|
2204
2211
|
const body = {
|
|
2205
2212
|
ticketId,
|
|
2206
2213
|
agentIdentifier: resolveProtocolAgentIdentifier(flags),
|
|
2207
2214
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
2215
|
+
...(externalSessionId !== undefined ? { externalSessionId } : {}),
|
|
2208
2216
|
metadata: await resolveProtocolMetadata(flags, { cwd: process.cwd() })
|
|
2209
2217
|
};
|
|
2210
2218
|
|
|
@@ -2813,9 +2821,12 @@ connect:
|
|
|
2813
2821
|
--agent <identifier>
|
|
2814
2822
|
--model <identifier>
|
|
2815
2823
|
--method <connectionMethod>
|
|
2824
|
+
--external-session-id <id|null> Store the native agent thread/session id, or clear it with null
|
|
2816
2825
|
--metadata-json <json> Extra session metadata object
|
|
2817
2826
|
Returns:
|
|
2818
2827
|
Session JSON and SESSION_KEY on stderr when available
|
|
2828
|
+
Notes:
|
|
2829
|
+
If --external-session-id is omitted, the CLI may auto-detect Codex or Claude session ids
|
|
2819
2830
|
|
|
2820
2831
|
load-context:
|
|
2821
2832
|
Purpose:
|
|
@@ -2938,6 +2949,7 @@ hook-event:
|
|
|
2938
2949
|
--prompt <text>
|
|
2939
2950
|
--turn-index <n>
|
|
2940
2951
|
--follow-up-intent <mode> discussion | execution | pending_delivery
|
|
2952
|
+
--external-session-id <id|null> Native agent resume/session id to persist on the attached session
|
|
2941
2953
|
|
|
2942
2954
|
read-context:
|
|
2943
2955
|
Purpose:
|
package/package.json
CHANGED
|
@@ -454,6 +454,11 @@ const tools = [
|
|
|
454
454
|
type: 'string',
|
|
455
455
|
enum: ['discussion', 'execution', 'pending_delivery']
|
|
456
456
|
},
|
|
457
|
+
external_session_id: {
|
|
458
|
+
type: ['string', 'null'],
|
|
459
|
+
description:
|
|
460
|
+
'Optional native agent resume/session id to persist on the attached Overlord session.'
|
|
461
|
+
},
|
|
457
462
|
session_key: {
|
|
458
463
|
type: 'string',
|
|
459
464
|
description:
|
|
@@ -468,6 +473,7 @@ const tools = [
|
|
|
468
473
|
prompt: args.prompt,
|
|
469
474
|
'turn-index': args.turn_index,
|
|
470
475
|
'follow-up-intent': args.follow_up_intent,
|
|
476
|
+
'external-session-id': args.external_session_id,
|
|
471
477
|
'session-key': args.session_key
|
|
472
478
|
}),
|
|
473
479
|
subcommand: 'hook-event'
|
|
@@ -72,7 +72,29 @@ if turn_index is None:
|
|
|
72
72
|
handle.write(str(turn_index))
|
|
73
73
|
|
|
74
74
|
tid = os.environ.get('TICKET_ID', '')
|
|
75
|
-
|
|
75
|
+
session_key = os.environ.get('SESSION_KEY') or ''
|
|
76
|
+
if not session_key:
|
|
77
|
+
encoded = __import__('base64').urlsafe_b64encode(os.getcwd().encode()).decode().rstrip('=')
|
|
78
|
+
session_file = os.path.join(__import__('tempfile').gettempdir(), f'.overlord-session-{encoded}')
|
|
79
|
+
try:
|
|
80
|
+
with open(session_file, encoding='utf-8') as handle:
|
|
81
|
+
persisted = json.load(handle)
|
|
82
|
+
if persisted.get('ticketId') == tid:
|
|
83
|
+
session_key = persisted.get('sessionKey') or ''
|
|
84
|
+
except Exception:
|
|
85
|
+
session_key = ''
|
|
86
|
+
|
|
87
|
+
payload = {
|
|
88
|
+
'hookType': 'UserPromptSubmit',
|
|
89
|
+
'ticketId': tid,
|
|
90
|
+
'prompt': text,
|
|
91
|
+
'turnIndex': turn_index,
|
|
92
|
+
}
|
|
93
|
+
if sid != 'unknown':
|
|
94
|
+
payload['externalSessionId'] = sid
|
|
95
|
+
if session_key:
|
|
96
|
+
payload['sessionKey'] = session_key
|
|
97
|
+
print(json.dumps(payload))
|
|
76
98
|
" 2>/dev/null
|
|
77
99
|
)
|
|
78
100
|
if [ -z "$PAYLOAD" ]; then
|
|
@@ -62,7 +62,29 @@ with open(path, 'w', encoding='utf-8') as handle:
|
|
|
62
62
|
handle.write(str(turn_index))
|
|
63
63
|
|
|
64
64
|
tid = os.environ.get('TICKET_ID', '')
|
|
65
|
-
|
|
65
|
+
session_key = os.environ.get('SESSION_KEY') or ''
|
|
66
|
+
if not session_key:
|
|
67
|
+
encoded = __import__('base64').urlsafe_b64encode(os.getcwd().encode()).decode().rstrip('=')
|
|
68
|
+
session_file = os.path.join(__import__('tempfile').gettempdir(), f'.overlord-session-{encoded}')
|
|
69
|
+
try:
|
|
70
|
+
with open(session_file, encoding='utf-8') as handle:
|
|
71
|
+
persisted = json.load(handle)
|
|
72
|
+
if persisted.get('ticketId') == tid:
|
|
73
|
+
session_key = persisted.get('sessionKey') or ''
|
|
74
|
+
except Exception:
|
|
75
|
+
session_key = ''
|
|
76
|
+
|
|
77
|
+
payload = {
|
|
78
|
+
'hookType': 'UserPromptSubmit',
|
|
79
|
+
'ticketId': tid,
|
|
80
|
+
'prompt': text,
|
|
81
|
+
'turnIndex': turn_index,
|
|
82
|
+
}
|
|
83
|
+
if sid != 'unknown':
|
|
84
|
+
payload['externalSessionId'] = sid
|
|
85
|
+
if session_key:
|
|
86
|
+
payload['sessionKey'] = session_key
|
|
87
|
+
print(json.dumps(payload))
|
|
66
88
|
" 2>/dev/null
|
|
67
89
|
)
|
|
68
90
|
if [ -z "$PAYLOAD" ]; then
|
|
@@ -74,7 +74,7 @@ Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`,
|
|
|
74
74
|
ovld protocol revert --objective-id <objective-id>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`revert` restores the local working tree to the recorded objective
|
|
77
|
+
`revert` restores the local working tree to the recorded objective state.
|
|
78
78
|
|
|
79
79
|
## Record Change Rationales
|
|
80
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Execution Targets And Checkout Paths
|
|
2
2
|
|
|
3
|
-
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by
|
|
3
|
+
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"
|
|
@@ -46,7 +46,29 @@ with open(path, 'w', encoding='utf-8') as handle:
|
|
|
46
46
|
handle.write(str(turn_index))
|
|
47
47
|
|
|
48
48
|
tid = os.environ.get('TICKET_ID', '')
|
|
49
|
-
|
|
49
|
+
session_key = os.environ.get('SESSION_KEY') or ''
|
|
50
|
+
if not session_key:
|
|
51
|
+
encoded = __import__('base64').urlsafe_b64encode(os.getcwd().encode()).decode().rstrip('=')
|
|
52
|
+
session_file = os.path.join(__import__('tempfile').gettempdir(), f'.overlord-session-{encoded}')
|
|
53
|
+
try:
|
|
54
|
+
with open(session_file, encoding='utf-8') as handle:
|
|
55
|
+
persisted = json.load(handle)
|
|
56
|
+
if persisted.get('ticketId') == tid:
|
|
57
|
+
session_key = persisted.get('sessionKey') or ''
|
|
58
|
+
except Exception:
|
|
59
|
+
session_key = ''
|
|
60
|
+
|
|
61
|
+
payload = {
|
|
62
|
+
'hookType': 'UserPromptSubmit',
|
|
63
|
+
'ticketId': tid,
|
|
64
|
+
'prompt': text,
|
|
65
|
+
'turnIndex': turn_index,
|
|
66
|
+
}
|
|
67
|
+
if cid != 'unknown':
|
|
68
|
+
payload['externalSessionId'] = cid
|
|
69
|
+
if session_key:
|
|
70
|
+
payload['sessionKey'] = session_key
|
|
71
|
+
print(json.dumps(payload))
|
|
50
72
|
" 2>/dev/null
|
|
51
73
|
)
|
|
52
74
|
if [ -n "$PAYLOAD" ]; then
|
|
@@ -74,7 +74,7 @@ Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`,
|
|
|
74
74
|
ovld protocol revert --objective-id <objective-id>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`revert` restores the local working tree to the recorded objective
|
|
77
|
+
`revert` restores the local working tree to the recorded objective state.
|
|
78
78
|
|
|
79
79
|
## Record Change Rationales
|
|
80
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Execution Targets And Checkout Paths
|
|
2
2
|
|
|
3
|
-
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by
|
|
3
|
+
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"
|
|
@@ -139,6 +139,7 @@ const tools = [
|
|
|
139
139
|
},
|
|
140
140
|
agent: { type: 'string' },
|
|
141
141
|
method: { type: 'string' },
|
|
142
|
+
external_session_id: { type: ['string', 'null'] },
|
|
142
143
|
metadata: {
|
|
143
144
|
type: 'object',
|
|
144
145
|
description:
|
|
@@ -151,6 +152,7 @@ const tools = [
|
|
|
151
152
|
'ticket-id': args.ticket_id,
|
|
152
153
|
agent: args.agent,
|
|
153
154
|
method: args.method,
|
|
155
|
+
'external-session-id': args.external_session_id,
|
|
154
156
|
...(args.metadata && typeof args.metadata === 'object' && !Array.isArray(args.metadata)
|
|
155
157
|
? { 'metadata-json': args.metadata }
|
|
156
158
|
: {})
|
|
@@ -179,7 +181,7 @@ const tools = [
|
|
|
179
181
|
{
|
|
180
182
|
name: 'revert',
|
|
181
183
|
description:
|
|
182
|
-
'Restore the local working tree to
|
|
184
|
+
'Restore the local working tree to the recorded objective state (maps to ovld protocol revert).',
|
|
183
185
|
inputSchema: {
|
|
184
186
|
type: 'object',
|
|
185
187
|
properties: {
|
|
@@ -454,6 +456,11 @@ const tools = [
|
|
|
454
456
|
type: 'string',
|
|
455
457
|
enum: ['discussion', 'execution', 'pending_delivery']
|
|
456
458
|
},
|
|
459
|
+
external_session_id: {
|
|
460
|
+
type: ['string', 'null'],
|
|
461
|
+
description:
|
|
462
|
+
'Optional native agent resume/session id to persist on the attached Overlord session.'
|
|
463
|
+
},
|
|
457
464
|
session_key: {
|
|
458
465
|
type: 'string',
|
|
459
466
|
description:
|
|
@@ -468,6 +475,7 @@ const tools = [
|
|
|
468
475
|
prompt: args.prompt,
|
|
469
476
|
'turn-index': args.turn_index,
|
|
470
477
|
'follow-up-intent': args.follow_up_intent,
|
|
478
|
+
'external-session-id': args.external_session_id,
|
|
471
479
|
'session-key': args.session_key
|
|
472
480
|
}),
|
|
473
481
|
subcommand: 'hook-event'
|
|
@@ -28,7 +28,40 @@ if [ -z "$OVERLORD_BASE_URL" ] || [ -z "$OVERLORD_TOKEN" ] || [ -z "${TICKET_ID:
|
|
|
28
28
|
fi
|
|
29
29
|
|
|
30
30
|
if [ -n "$OVERLORD_BASE_URL" ] && [ -n "$OVERLORD_TOKEN" ] && [ -n "$TICKET_ID" ]; then
|
|
31
|
-
PAYLOAD=$(printf '%s' "$BODY" | python3 -c "
|
|
31
|
+
PAYLOAD=$(printf '%s' "$BODY" | python3 -c "
|
|
32
|
+
import base64
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
import tempfile
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
body = json.load(sys.stdin)
|
|
40
|
+
external_session_id = os.environ.get('CODEX_THREAD_ID') or os.environ.get('CODEX_SESSION_ID') or None
|
|
41
|
+
session_key = os.environ.get('SESSION_KEY') or ''
|
|
42
|
+
|
|
43
|
+
if not session_key:
|
|
44
|
+
encoded = base64.urlsafe_b64encode(os.getcwd().encode()).decode().rstrip('=')
|
|
45
|
+
session_file = Path(tempfile.gettempdir()) / f'.overlord-session-{encoded}'
|
|
46
|
+
try:
|
|
47
|
+
persisted = json.loads(session_file.read_text(encoding='utf-8'))
|
|
48
|
+
if persisted.get('ticketId') == os.environ.get('TICKET_ID'):
|
|
49
|
+
session_key = persisted.get('sessionKey') or ''
|
|
50
|
+
except Exception:
|
|
51
|
+
session_key = ''
|
|
52
|
+
|
|
53
|
+
payload = {
|
|
54
|
+
'hookType': 'UserPromptSubmit',
|
|
55
|
+
'ticketId': os.environ['TICKET_ID'],
|
|
56
|
+
'prompt': body.get('prompt', ''),
|
|
57
|
+
'turnIndex': body.get('turn_number', 0),
|
|
58
|
+
}
|
|
59
|
+
if external_session_id:
|
|
60
|
+
payload['externalSessionId'] = external_session_id
|
|
61
|
+
if session_key:
|
|
62
|
+
payload['sessionKey'] = session_key
|
|
63
|
+
print(json.dumps(payload))
|
|
64
|
+
" 2>/dev/null || echo '')
|
|
32
65
|
if [ -z "$PAYLOAD" ]; then
|
|
33
66
|
log_hook "failed to build hook-event payload from submit body"
|
|
34
67
|
exit 0
|
|
@@ -74,7 +74,7 @@ Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`,
|
|
|
74
74
|
ovld protocol revert --objective-id <objective-id>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`revert` restores the local working tree to the recorded objective
|
|
77
|
+
`revert` restores the local working tree to the recorded objective state.
|
|
78
78
|
|
|
79
79
|
## Record Change Rationales
|
|
80
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Execution Targets And Checkout Paths
|
|
2
2
|
|
|
3
|
-
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by
|
|
3
|
+
Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"
|