overlord-cli 0.2606010745.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 +3 -1
- package/bin/_cli/index.mjs +7 -0
- package/bin/_cli/onboard.mjs +245 -0
- package/bin/_cli/protocol.mjs +24 -0
- package/bin/_cli/runner.mjs +34 -11
- 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
|
|
@@ -158,6 +158,8 @@ Devices are keyed by **(organization, user, fingerprint)** so the same physical
|
|
|
158
158
|
|
|
159
159
|
`ovld runner start` keeps a foreground runner alive for CLI-only or remote hosts. It claims compatible execution requests for the current device and launches the assigned agent/model with `ovld launch`; `ovld runner once` claims at most one request and exits. `ovld runner status` now prints the local device record plus the active queue visible to that runner. `ovld runner clear <objective_id>` clears one queued objective, and `ovld runner clear-all` clears every active queue row visible to the caller. By default a single runner drains queued requests from **every organization you belong to**, so you do not need one runner per org. Pin it to a single org with `--organization-id <id>` or `OVERLORD_ORGANIZATION_ID`.
|
|
160
160
|
|
|
161
|
+
If you are troubleshooting "why is `ovld runner` randomly executing", the runner is usually claiming a request that was already queued by manual Run or auto-advance. Inspect the queue with `ovld runner status`, then clear the specific objective with `ovld runner clear 8974e557-bec4-4984-b12c-be46bd63207c` or `ovld protocol clear-execution-requests --objective-id 8974e557-bec4-4984-b12c-be46bd63207c`.
|
|
162
|
+
|
|
161
163
|
Use `create` for future work you want to track, `prompt` for work that should start immediately, and `record-work` for work that was already completed in chat and now needs a review ticket plus feed post. Use multiple tickets when prompts represent different features or goals; use `add-objectives` or `--objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'` when prompts are sequential steps toward the same feature or goal.
|
|
162
164
|
|
|
163
165
|
`ovld protocol deliver` accepts either discrete flags like `--summary` / `--artifacts-json`, an inline full payload with `--payload-json '{"summary":"...","artifacts":[...],"changeRationales":[...]}'`, or a file/stdin payload with `--payload-file <path|->`.
|
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
|
@@ -85,6 +85,18 @@ async function resolveProtocolMetadata(flags = {}, base = {}) {
|
|
|
85
85
|
metadata.model = modelIdentifier;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Thread the originating execution request id (set by the runner via
|
|
89
|
+
// OVERLORD_EXECUTION_REQUEST_ID, or an explicit flag) into attach metadata so
|
|
90
|
+
// the server marks that exact request `launched` instead of falling back to
|
|
91
|
+
// matching by objective.
|
|
92
|
+
const executionRequestId =
|
|
93
|
+
(typeof flags['execution-request-id'] === 'string'
|
|
94
|
+
? flags['execution-request-id'].trim()
|
|
95
|
+
: '') || process.env.OVERLORD_EXECUTION_REQUEST_ID?.trim();
|
|
96
|
+
if (executionRequestId) {
|
|
97
|
+
metadata.executionRequestId = executionRequestId;
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
const snapshot = await resolveSnapshotContext(flags);
|
|
89
101
|
if (snapshot) {
|
|
90
102
|
metadata.snapshot = snapshot;
|
|
@@ -1284,6 +1296,12 @@ async function protocolHookEvent(args) {
|
|
|
1284
1296
|
? { turnIndex: parseInt(String(flags['turn-index']), 10) }
|
|
1285
1297
|
: {}),
|
|
1286
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
|
+
})(),
|
|
1287
1305
|
...(sessionKey ? { sessionKey } : {})
|
|
1288
1306
|
};
|
|
1289
1307
|
|
|
@@ -2188,11 +2206,13 @@ async function protocolConnect(args) {
|
|
|
2188
2206
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
2189
2207
|
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
2190
2208
|
const timeoutMs = resolveTimeout(flags);
|
|
2209
|
+
const externalSessionId = resolveExternalSessionId(flags);
|
|
2191
2210
|
|
|
2192
2211
|
const body = {
|
|
2193
2212
|
ticketId,
|
|
2194
2213
|
agentIdentifier: resolveProtocolAgentIdentifier(flags),
|
|
2195
2214
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
2215
|
+
...(externalSessionId !== undefined ? { externalSessionId } : {}),
|
|
2196
2216
|
metadata: await resolveProtocolMetadata(flags, { cwd: process.cwd() })
|
|
2197
2217
|
};
|
|
2198
2218
|
|
|
@@ -2801,9 +2821,12 @@ connect:
|
|
|
2801
2821
|
--agent <identifier>
|
|
2802
2822
|
--model <identifier>
|
|
2803
2823
|
--method <connectionMethod>
|
|
2824
|
+
--external-session-id <id|null> Store the native agent thread/session id, or clear it with null
|
|
2804
2825
|
--metadata-json <json> Extra session metadata object
|
|
2805
2826
|
Returns:
|
|
2806
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
|
|
2807
2830
|
|
|
2808
2831
|
load-context:
|
|
2809
2832
|
Purpose:
|
|
@@ -2926,6 +2949,7 @@ hook-event:
|
|
|
2926
2949
|
--prompt <text>
|
|
2927
2950
|
--turn-index <n>
|
|
2928
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
|
|
2929
2953
|
|
|
2930
2954
|
read-context:
|
|
2931
2955
|
Purpose:
|
package/bin/_cli/runner.mjs
CHANGED
|
@@ -171,13 +171,17 @@ function resolvePinnedOrganizationId(flags) {
|
|
|
171
171
|
const ORGANIZATION_REFRESH_MS = 60_000;
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* Resolves which organizations a
|
|
174
|
+
* Resolves which organizations a fan-out pass (list / clear-all) should act on.
|
|
175
175
|
*
|
|
176
176
|
* - When the user pins an org (`--organization-id` flag or
|
|
177
|
-
* `OVERLORD_ORGANIZATION_ID`), only that org is
|
|
178
|
-
* - Otherwise the runner discovers every org the authenticated user belongs to
|
|
179
|
-
*
|
|
180
|
-
*
|
|
177
|
+
* `OVERLORD_ORGANIZATION_ID`), only that org is used.
|
|
178
|
+
* - Otherwise the runner discovers every org the authenticated user belongs to.
|
|
179
|
+
* If discovery fails or returns nothing, it falls back to the credential
|
|
180
|
+
* default (an empty scope id) so behavior never regresses.
|
|
181
|
+
*
|
|
182
|
+
* NOTE: this is for the per-org fan-out commands. The claim poll uses
|
|
183
|
+
* {@link createClaimOrganizationScope}, which is org-agnostic — the server's
|
|
184
|
+
* `claim-execution` is authoritative and spans the user's target-sharing orgs.
|
|
181
185
|
*/
|
|
182
186
|
export function createOrganizationScope(flags, deviceFingerprint) {
|
|
183
187
|
const pinned = resolvePinnedOrganizationId(flags);
|
|
@@ -211,6 +215,17 @@ export function createOrganizationScope(flags, deviceFingerprint) {
|
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Resolves the org scope for the claim poll (G3). A pinned org still scopes the
|
|
220
|
+
* poll; otherwise the runner does a single org-agnostic poll (empty scope) and
|
|
221
|
+
* lets the server claim across every org the user belongs to that shares the
|
|
222
|
+
* claiming target — no org discovery or per-org looping needed.
|
|
223
|
+
*/
|
|
224
|
+
export function createClaimOrganizationScope(flags) {
|
|
225
|
+
const pinned = resolvePinnedOrganizationId(flags);
|
|
226
|
+
return { pinned: Boolean(pinned), resolve: () => [pinned || ''] };
|
|
227
|
+
}
|
|
228
|
+
|
|
214
229
|
function completeLaunch(requestId, deviceFingerprint) {
|
|
215
230
|
protocolJson(
|
|
216
231
|
'complete-execution-launch',
|
|
@@ -276,11 +291,19 @@ function normalizeRunnerTerminalProfile(value) {
|
|
|
276
291
|
};
|
|
277
292
|
}
|
|
278
293
|
|
|
279
|
-
function buildRunnerLaunchShellCommand(args, deviceFingerprint) {
|
|
280
|
-
|
|
281
|
-
|
|
294
|
+
function buildRunnerLaunchShellCommand(args, deviceFingerprint, executionRequestId) {
|
|
295
|
+
const lines = [`export OVERLORD_DEVICE_FINGERPRINT=${shellQuote(deviceFingerprint)}`];
|
|
296
|
+
// Terminal-profile launches run this generated script in a fresh shell, so the
|
|
297
|
+
// request id must be exported here too (the direct-spawn path inherits it via
|
|
298
|
+
// the child env). Without it, attach in the new terminal falls back to
|
|
299
|
+
// objective matching.
|
|
300
|
+
if (executionRequestId) {
|
|
301
|
+
lines.push(`export OVERLORD_EXECUTION_REQUEST_ID=${shellQuote(executionRequestId)}`);
|
|
302
|
+
}
|
|
303
|
+
lines.push(
|
|
282
304
|
`exec ${shellQuote(process.execPath)} ${[OVLD_ENTRY, ...args].map(shellQuote).join(' ')}`
|
|
283
|
-
|
|
305
|
+
);
|
|
306
|
+
return lines.join('\n');
|
|
284
307
|
}
|
|
285
308
|
|
|
286
309
|
function writeRunnerLaunchScript(launchCommand) {
|
|
@@ -375,7 +398,7 @@ export function buildRunnerTerminalOpenCommand(
|
|
|
375
398
|
|
|
376
399
|
function spawnLaunchProcess(args, claim, deviceFingerprint) {
|
|
377
400
|
const spawnImpl = runnerTestHooks.spawn ?? spawn;
|
|
378
|
-
const launchCommand = buildRunnerLaunchShellCommand(args, deviceFingerprint);
|
|
401
|
+
const launchCommand = buildRunnerLaunchShellCommand(args, deviceFingerprint, claim.request?.id);
|
|
379
402
|
const terminalCommand = buildRunnerTerminalOpenCommand(
|
|
380
403
|
claim.launch?.runnerTerminalProfile,
|
|
381
404
|
launchCommand,
|
|
@@ -466,7 +489,7 @@ export async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
|
466
489
|
}
|
|
467
490
|
|
|
468
491
|
export async function runOnce(flags, deviceFingerprint, organizationScope) {
|
|
469
|
-
const scope = organizationScope ??
|
|
492
|
+
const scope = organizationScope ?? createClaimOrganizationScope(flags);
|
|
470
493
|
const organizationIds = scope.resolve();
|
|
471
494
|
|
|
472
495
|
let launchedAny = false;
|
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"
|