@agentworkforce/deploy 3.0.3 → 3.0.4
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/dist/connect.d.ts +11 -0
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +120 -2
- package/dist/connect.js.map +1 -1
- package/dist/connect.test.d.ts +2 -0
- package/dist/connect.test.d.ts.map +1 -0
- package/dist/connect.test.js +93 -0
- package/dist/connect.test.js.map +1 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +36 -28
- package/dist/deploy.js.map +1 -1
- package/dist/deploy.test.js +9 -4
- package/dist/deploy.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts +19 -10
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +172 -176
- package/dist/login.js.map +1 -1
- package/dist/login.test.d.ts +2 -0
- package/dist/login.test.d.ts.map +1 -0
- package/dist/login.test.js +126 -0
- package/dist/login.test.js.map +1 -0
- package/dist/modes/cloud.d.ts +9 -0
- package/dist/modes/cloud.d.ts.map +1 -1
- package/dist/modes/cloud.js +126 -194
- package/dist/modes/cloud.js.map +1 -1
- package/dist/modes/cloud.test.js +90 -77
- package/dist/modes/cloud.test.js.map +1 -1
- package/dist/modes/input-values.test.js +8 -1
- package/dist/modes/input-values.test.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { clearStoredWorkspaceToken, loadWorkspaceToken, resolveWorkspaceToken, writeStoredWorkspaceToken } from './login.js';
|
|
7
|
+
import { createBufferedIO } from './io.js';
|
|
8
|
+
async function withLoginEnv(env, fn) {
|
|
9
|
+
const previous = {
|
|
10
|
+
WORKFORCE_LOGIN_FILE: process.env.WORKFORCE_LOGIN_FILE,
|
|
11
|
+
WORKFORCE_DISABLE_KEYCHAIN: process.env.WORKFORCE_DISABLE_KEYCHAIN,
|
|
12
|
+
WORKFORCE_WORKSPACE_ID: process.env.WORKFORCE_WORKSPACE_ID,
|
|
13
|
+
WORKFORCE_WORKSPACE_TOKEN: process.env.WORKFORCE_WORKSPACE_TOKEN
|
|
14
|
+
};
|
|
15
|
+
process.env.WORKFORCE_DISABLE_KEYCHAIN = '1';
|
|
16
|
+
if (env.loginFile === undefined)
|
|
17
|
+
delete process.env.WORKFORCE_LOGIN_FILE;
|
|
18
|
+
else
|
|
19
|
+
process.env.WORKFORCE_LOGIN_FILE = env.loginFile;
|
|
20
|
+
if (env.workspaceId === undefined)
|
|
21
|
+
delete process.env.WORKFORCE_WORKSPACE_ID;
|
|
22
|
+
else
|
|
23
|
+
process.env.WORKFORCE_WORKSPACE_ID = env.workspaceId;
|
|
24
|
+
if (env.workspaceToken === undefined)
|
|
25
|
+
delete process.env.WORKFORCE_WORKSPACE_TOKEN;
|
|
26
|
+
else
|
|
27
|
+
process.env.WORKFORCE_WORKSPACE_TOKEN = env.workspaceToken;
|
|
28
|
+
try {
|
|
29
|
+
return await fn();
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
delete process.env[key];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
process.env[key] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
test('workspace token store writes and reads the active workspace token', async () => {
|
|
43
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'wf-login-store-'));
|
|
44
|
+
const loginFile = path.join(dir, 'login.json');
|
|
45
|
+
try {
|
|
46
|
+
await withLoginEnv({ loginFile }, async () => {
|
|
47
|
+
await writeStoredWorkspaceToken({
|
|
48
|
+
workspaceSlug: 'acme',
|
|
49
|
+
workspaceId: 'ws-123',
|
|
50
|
+
token: 'tok-stored',
|
|
51
|
+
cloudUrl: 'https://cloud.example.test'
|
|
52
|
+
});
|
|
53
|
+
const raw = JSON.parse(await readFile(loginFile, 'utf8'));
|
|
54
|
+
assert.equal(raw.workspace, 'acme');
|
|
55
|
+
assert.equal(raw.workspaceId, 'ws-123');
|
|
56
|
+
assert.equal(raw.token, 'tok-stored');
|
|
57
|
+
assert.equal((await loadWorkspaceToken('acme'))?.token, 'tok-stored');
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await rm(dir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
test('resolveWorkspaceToken prefers env token before stored login', async () => {
|
|
65
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'wf-login-precedence-'));
|
|
66
|
+
const loginFile = path.join(dir, 'login.json');
|
|
67
|
+
try {
|
|
68
|
+
await withLoginEnv({ loginFile }, async () => {
|
|
69
|
+
await writeStoredWorkspaceToken({ workspace: 'stored', token: 'tok-stored' });
|
|
70
|
+
});
|
|
71
|
+
await withLoginEnv({
|
|
72
|
+
loginFile,
|
|
73
|
+
workspaceId: 'env-ws',
|
|
74
|
+
workspaceToken: 'tok-env'
|
|
75
|
+
}, async () => {
|
|
76
|
+
assert.deepEqual(await resolveWorkspaceToken({
|
|
77
|
+
cloudUrl: 'https://cloud.example.test',
|
|
78
|
+
io: createBufferedIO()
|
|
79
|
+
}), { token: 'tok-env', workspace: 'env-ws' });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await rm(dir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
test('resolveWorkspaceToken reads stored token and fails clearly with --no-prompt', async () => {
|
|
87
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'wf-login-resolve-'));
|
|
88
|
+
const loginFile = path.join(dir, 'login.json');
|
|
89
|
+
try {
|
|
90
|
+
await withLoginEnv({ loginFile }, async () => {
|
|
91
|
+
await writeStoredWorkspaceToken({ workspace: 'stored', token: 'tok-stored' });
|
|
92
|
+
assert.deepEqual(await resolveWorkspaceToken({
|
|
93
|
+
workspace: 'stored',
|
|
94
|
+
cloudUrl: 'https://cloud.example.test',
|
|
95
|
+
io: createBufferedIO(),
|
|
96
|
+
noPrompt: true
|
|
97
|
+
}), { token: 'tok-stored', workspace: 'stored' });
|
|
98
|
+
});
|
|
99
|
+
await withLoginEnv({ loginFile: path.join(dir, 'missing.json') }, async () => {
|
|
100
|
+
await assert.rejects(resolveWorkspaceToken({
|
|
101
|
+
workspace: 'missing',
|
|
102
|
+
cloudUrl: 'https://cloud.example.test',
|
|
103
|
+
io: createBufferedIO(),
|
|
104
|
+
noPrompt: true
|
|
105
|
+
}), /run `agentworkforce login`/);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await rm(dir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
test('clearStoredWorkspaceToken removes the stored token file', async () => {
|
|
113
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'wf-login-clear-'));
|
|
114
|
+
const loginFile = path.join(dir, 'login.json');
|
|
115
|
+
try {
|
|
116
|
+
await withLoginEnv({ loginFile }, async () => {
|
|
117
|
+
await writeStoredWorkspaceToken({ workspace: 'stored', token: 'tok-stored' });
|
|
118
|
+
await clearStoredWorkspaceToken('stored');
|
|
119
|
+
assert.equal(await loadWorkspaceToken('stored'), null);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await rm(dir, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=login.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../src/login.test.ts"],"names":[],"mappings":"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"}
|
package/dist/modes/cloud.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CloudApiClient, connectProvider, readStoredAuth, refreshStoredAuth, type StoredAuth } from '@agent-relay/cloud';
|
|
1
2
|
import type { ModeLaunchHandle, ModeLauncher } from '../types.js';
|
|
2
3
|
type CloudDeployStatus = 'starting' | 'active' | 'failed' | 'cancelled';
|
|
3
4
|
export interface CloudRunHandle extends ModeLaunchHandle {
|
|
@@ -5,6 +6,14 @@ export interface CloudRunHandle extends ModeLaunchHandle {
|
|
|
5
6
|
deploymentId: string;
|
|
6
7
|
status: CloudDeployStatus;
|
|
7
8
|
}
|
|
9
|
+
type CloudApiClientLike = Pick<CloudApiClient, 'fetch'>;
|
|
10
|
+
type CloudCredentialDeps = {
|
|
11
|
+
readStoredAuth: typeof readStoredAuth;
|
|
12
|
+
refreshStoredAuth: typeof refreshStoredAuth;
|
|
13
|
+
connectProvider: typeof connectProvider;
|
|
14
|
+
createCloudApiClient(auth: StoredAuth, apiUrl: string): CloudApiClientLike;
|
|
15
|
+
};
|
|
16
|
+
export declare function configureCloudCredentialDepsForTest(overrides: Partial<CloudCredentialDeps>): () => void;
|
|
8
17
|
/**
|
|
9
18
|
* Cloud-hosted deploy mode. Uploads the deploy-ready persona bundle to a
|
|
10
19
|
* workforce-compatible cloud endpoint. The implementation is intentionally
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/modes/cloud.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/modes/cloud.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,eAAe,EAEf,cAAc,EACd,iBAAiB,EACjB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAEV,gBAAgB,EAChB,YAAY,EACb,MAAM,aAAa,CAAC;AAYrB,KAAK,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAIxE,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAsCD,KAAK,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAExD,KAAK,mBAAmB,GAAG;IACzB,cAAc,EAAE,OAAO,cAAc,CAAC;IACtC,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAAC;CAC5E,CAAC;AAkBF,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACtC,MAAM,IAAI,CAMZ;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,YA8H3B,CAAC"}
|
package/dist/modes/cloud.js
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
1
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
5
|
-
import { platform } from 'node:os';
|
|
2
|
+
import { CloudApiClient, connectProvider, defaultApiUrl, readStoredAuth, refreshStoredAuth } from '@agent-relay/cloud';
|
|
6
3
|
import { resolveWorkspaceToken } from '../login.js';
|
|
7
|
-
const DEFAULT_CLOUD_URL = 'https://agentrelay.com';
|
|
8
4
|
const BUILD_YOUR_OWN_CLOUD_DOCS_URL = 'https://docs.agentworkforce.com/deploy/build-your-own-cloud';
|
|
9
5
|
const USER_AGENT = 'workforce-deploy';
|
|
10
6
|
const MAX_ATTEMPTS = 3;
|
|
11
7
|
const POLL_TIMEOUT_MS = 60_000;
|
|
12
8
|
const POLL_INTERVAL_MS = 2_000;
|
|
9
|
+
const defaultCloudCredentialDeps = {
|
|
10
|
+
readStoredAuth,
|
|
11
|
+
refreshStoredAuth,
|
|
12
|
+
connectProvider,
|
|
13
|
+
createCloudApiClient(auth, apiUrl) {
|
|
14
|
+
return new CloudApiClient({
|
|
15
|
+
apiUrl,
|
|
16
|
+
accessToken: auth.accessToken,
|
|
17
|
+
refreshToken: auth.refreshToken,
|
|
18
|
+
accessTokenExpiresAt: auth.accessTokenExpiresAt
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
let cloudCredentialDeps = defaultCloudCredentialDeps;
|
|
23
|
+
export function configureCloudCredentialDepsForTest(overrides) {
|
|
24
|
+
const previous = cloudCredentialDeps;
|
|
25
|
+
cloudCredentialDeps = { ...cloudCredentialDeps, ...overrides };
|
|
26
|
+
return () => {
|
|
27
|
+
cloudCredentialDeps = previous;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
13
30
|
/**
|
|
14
31
|
* Cloud-hosted deploy mode. Uploads the deploy-ready persona bundle to a
|
|
15
32
|
* workforce-compatible cloud endpoint. The implementation is intentionally
|
|
@@ -29,7 +46,7 @@ export const cloudLauncher = {
|
|
|
29
46
|
io: input.io,
|
|
30
47
|
noPrompt
|
|
31
48
|
});
|
|
32
|
-
await ensureHarnessReady({
|
|
49
|
+
const credentialSelections = await ensureHarnessReady({
|
|
33
50
|
cloudUrl,
|
|
34
51
|
workspaceId: input.workspace,
|
|
35
52
|
token: auth.token,
|
|
@@ -39,14 +56,6 @@ export const cloudLauncher = {
|
|
|
39
56
|
harnessSource: input.harnessSource,
|
|
40
57
|
byokKey: input.byokKey
|
|
41
58
|
});
|
|
42
|
-
await ensureCloudIntegrations({
|
|
43
|
-
cloudUrl,
|
|
44
|
-
workspaceId: input.workspace,
|
|
45
|
-
token: auth.token,
|
|
46
|
-
persona: input.persona,
|
|
47
|
-
io: input.io,
|
|
48
|
-
noPrompt
|
|
49
|
-
});
|
|
50
59
|
const existingPersona = await handleExistingPersona({
|
|
51
60
|
cloudUrl,
|
|
52
61
|
workspaceId: input.workspace,
|
|
@@ -76,6 +85,10 @@ export const cloudLauncher = {
|
|
|
76
85
|
agent: await readFile(input.bundle.bundlePath, 'utf8'),
|
|
77
86
|
packageJson: JSON.parse(await readFile(input.bundle.packageJsonPath, 'utf8'))
|
|
78
87
|
},
|
|
88
|
+
// Keep both casings until all cloud deploy endpoints converge; older
|
|
89
|
+
// previews read snake_case, while current routes read camelCase.
|
|
90
|
+
credentialSelections,
|
|
91
|
+
credential_selections: credentialSelections,
|
|
79
92
|
inputs: input.inputs ?? readInputsOverride()
|
|
80
93
|
});
|
|
81
94
|
input.io.info(`cloud: deploying persona bundle to ${cloudUrl}`);
|
|
@@ -139,9 +152,9 @@ function resolveCloudUrl(input) {
|
|
|
139
152
|
const fromEnv = process.env.WORKFORCE_DEPLOY_CLOUD_URL?.trim()
|
|
140
153
|
|| process.env.WORKFORCE_CLOUD_URL?.trim();
|
|
141
154
|
const fromPersona = readPersonaCloudDeployUrl(input.persona);
|
|
142
|
-
const raw = fromInput || fromEnv || fromPersona ||
|
|
155
|
+
const raw = fromInput || fromEnv || fromPersona || defaultApiUrl();
|
|
143
156
|
const resolved = normalizeCloudUrl(raw);
|
|
144
|
-
if (resolved !==
|
|
157
|
+
if (resolved !== normalizeCloudUrl(defaultApiUrl())) {
|
|
145
158
|
input.io.info(`cloud: using custom cloud URL ${resolved}. Build your own cloud docs: ${BUILD_YOUR_OWN_CLOUD_DOCS_URL}`);
|
|
146
159
|
}
|
|
147
160
|
return resolved;
|
|
@@ -156,28 +169,31 @@ async function ensureHarnessReady(args) {
|
|
|
156
169
|
const source = await resolveHarnessSource(args);
|
|
157
170
|
const modelProvider = deriveModelProvider(args.persona);
|
|
158
171
|
if (source === 'plan') {
|
|
159
|
-
await saveProviderCredential({
|
|
172
|
+
const credentialId = await saveProviderCredential({
|
|
160
173
|
cloudUrl: args.cloudUrl,
|
|
174
|
+
workspaceId: args.workspaceId,
|
|
161
175
|
token: args.token,
|
|
162
176
|
modelProvider,
|
|
163
177
|
authType: 'relay_managed'
|
|
164
178
|
});
|
|
165
179
|
args.io.info(`cloud: using workforce plan credentials for ${args.persona.harness}`);
|
|
166
|
-
return;
|
|
180
|
+
return { [modelProvider]: credentialId };
|
|
167
181
|
}
|
|
168
182
|
if (source === 'byok') {
|
|
169
183
|
const key = await resolveByokKey(args);
|
|
170
|
-
await saveProviderCredential({
|
|
184
|
+
const credentialId = await saveProviderCredential({
|
|
171
185
|
cloudUrl: args.cloudUrl,
|
|
186
|
+
workspaceId: args.workspaceId,
|
|
172
187
|
token: args.token,
|
|
173
188
|
modelProvider,
|
|
174
189
|
authType: 'byo_api_key',
|
|
175
190
|
apiKey: key
|
|
176
191
|
});
|
|
177
192
|
args.io.info(`cloud: using BYOK credentials for ${args.persona.harness}`);
|
|
178
|
-
return;
|
|
193
|
+
return { [modelProvider]: credentialId };
|
|
179
194
|
}
|
|
180
195
|
await ensureHarnessOauth(args);
|
|
196
|
+
return {};
|
|
181
197
|
}
|
|
182
198
|
async function resolveHarnessSource(args) {
|
|
183
199
|
if (args.harnessSource)
|
|
@@ -195,13 +211,14 @@ async function resolveHarnessSource(args) {
|
|
|
195
211
|
return expectHarnessSource(answer);
|
|
196
212
|
}
|
|
197
213
|
async function isHarnessOauthConnected(args) {
|
|
198
|
-
const
|
|
199
|
-
|
|
214
|
+
const auth = await readUsableCloudAuth(args.cloudUrl);
|
|
215
|
+
if (!auth)
|
|
216
|
+
return false;
|
|
217
|
+
const client = cloudCredentialDeps.createCloudApiClient(auth, args.cloudUrl);
|
|
218
|
+
const path = `/api/v1/users/me/provider_credentials?model_provider=${encodeURIComponent(deriveModelProvider(args.persona))}`;
|
|
219
|
+
const res = await client.fetch(path, {
|
|
200
220
|
method: 'GET',
|
|
201
|
-
headers: {
|
|
202
|
-
authorization: `Bearer ${args.token}`,
|
|
203
|
-
'user-agent': USER_AGENT
|
|
204
|
-
}
|
|
221
|
+
headers: { 'user-agent': USER_AGENT }
|
|
205
222
|
});
|
|
206
223
|
if (res.status === 404 || res.status === 405)
|
|
207
224
|
return false;
|
|
@@ -242,76 +259,17 @@ async function ensureHarnessOauth(args) {
|
|
|
242
259
|
throw new Error(`cloud: ${args.persona.harness} credentials are required for deploy`);
|
|
243
260
|
}
|
|
244
261
|
const modelProvider = deriveModelProvider(args.persona);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
language: 'typescript'
|
|
253
|
-
})
|
|
254
|
-
}, { action: 'cloud harness OAuth start' });
|
|
255
|
-
const connectUrl = readFirstString(body, ['connectLink', 'authUrl', 'url', 'sandboxUrl']);
|
|
256
|
-
if (connectUrl) {
|
|
257
|
-
args.io.info(`cloud: open ${connectUrl} to finish ${args.persona.harness} OAuth`);
|
|
258
|
-
tryOpenBrowser(connectUrl);
|
|
259
|
-
}
|
|
260
|
-
await pollUntil(() => isHarnessOauthConnected(args), `timed out waiting for ${args.persona.harness} OAuth credentials`);
|
|
261
|
-
args.io.info(`cloud: ${args.persona.harness} credentials connected`);
|
|
262
|
-
}
|
|
263
|
-
async function ensureCloudIntegrations(args) {
|
|
264
|
-
const providers = Object.keys(args.persona.integrations ?? {});
|
|
265
|
-
for (const provider of providers) {
|
|
266
|
-
const ready = await isIntegrationReady({ ...args, provider });
|
|
267
|
-
if (ready) {
|
|
268
|
-
args.io.info(`cloud: integrations.${provider} ready`);
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
if (args.noPrompt) {
|
|
272
|
-
throw new Error(`cloud: integrations.${provider} is not connected. Run without --no-prompt or connect it before deploying.`);
|
|
273
|
-
}
|
|
274
|
-
const ok = await args.io.confirm(`Connect ${provider} in workforce cloud now? (opens browser)`, { defaultValue: true });
|
|
275
|
-
if (!ok) {
|
|
276
|
-
throw new Error(`cloud: integrations.${provider} is required for deploy`);
|
|
277
|
-
}
|
|
278
|
-
await connectIntegration({ ...args, provider });
|
|
279
|
-
await pollUntil(() => isIntegrationReady({ ...args, provider }), `timed out waiting for integrations.${provider} to become ready`);
|
|
280
|
-
args.io.info(`cloud: integrations.${provider} connected`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
async function isIntegrationReady(args) {
|
|
284
|
-
const url = `${args.cloudUrl}/api/v1/workspaces/${encodeURIComponent(args.workspaceId)}/integrations?provider=${encodeURIComponent(args.provider)}`;
|
|
285
|
-
const res = await fetch(url, {
|
|
286
|
-
method: 'GET',
|
|
287
|
-
headers: {
|
|
288
|
-
authorization: `Bearer ${args.token}`,
|
|
289
|
-
'user-agent': USER_AGENT
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
if (res.status === 401) {
|
|
293
|
-
throw new Error('cloud integration check failed: unauthorized. Run `workforce login` and retry.');
|
|
294
|
-
}
|
|
295
|
-
if (res.status === 404)
|
|
296
|
-
return false;
|
|
297
|
-
if (!res.ok) {
|
|
298
|
-
throw new Error(`cloud integration check failed: ${res.status} ${await responseExcerpt(res)}`);
|
|
299
|
-
}
|
|
300
|
-
const body = (await res.json());
|
|
301
|
-
return integrationReady(body, args.provider);
|
|
302
|
-
}
|
|
303
|
-
async function connectIntegration(args) {
|
|
304
|
-
await waitForOAuthCallback({
|
|
305
|
-
action: `integrations.${args.provider}`,
|
|
306
|
-
io: args.io,
|
|
307
|
-
buildUrl(returnTo) {
|
|
308
|
-
const url = new URL('/integrations', args.cloudUrl);
|
|
309
|
-
url.searchParams.set('provider', args.provider);
|
|
310
|
-
url.searchParams.set('workspace', args.workspaceId);
|
|
311
|
-
url.searchParams.set('return_to', returnTo);
|
|
312
|
-
return url.toString();
|
|
262
|
+
await cloudCredentialDeps.connectProvider({
|
|
263
|
+
provider: modelProvider,
|
|
264
|
+
apiUrl: args.cloudUrl,
|
|
265
|
+
language: 'typescript',
|
|
266
|
+
io: {
|
|
267
|
+
log: (...parts) => args.io.info(parts.map(String).join(' ')),
|
|
268
|
+
error: (...parts) => args.io.error(parts.map(String).join(' '))
|
|
313
269
|
}
|
|
314
270
|
});
|
|
271
|
+
await pollUntil(() => isHarnessOauthConnected(args), `timed out waiting for ${args.persona.harness} OAuth credentials`);
|
|
272
|
+
args.io.info(`cloud: ${args.persona.harness} credentials connected`);
|
|
315
273
|
}
|
|
316
274
|
async function handleExistingPersona(args) {
|
|
317
275
|
const existing = await findExistingAgent(args);
|
|
@@ -411,23 +369,67 @@ function parseAgentLike(value) {
|
|
|
411
369
|
};
|
|
412
370
|
}
|
|
413
371
|
async function saveProviderCredential(args) {
|
|
414
|
-
|
|
372
|
+
if (args.authType === 'relay_managed') {
|
|
373
|
+
const url = new URL(`${args.cloudUrl}/api/v1/workspaces/${encodeURIComponent(args.workspaceId)}/provider-credentials/managed`);
|
|
374
|
+
url.searchParams.set('provider', args.modelProvider);
|
|
375
|
+
const body = await requestJsonWithRetry(url.toString(), {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
headers: jsonHeaders(args.token)
|
|
378
|
+
}, { action: 'cloud managed provider credentials' });
|
|
379
|
+
return readCredentialId(body);
|
|
380
|
+
}
|
|
381
|
+
const body = await requestJsonWithRetry(`${args.cloudUrl}/api/v1/workspaces/${encodeURIComponent(args.workspaceId)}/provider-credentials/byok`, {
|
|
415
382
|
method: 'POST',
|
|
416
383
|
headers: jsonHeaders(args.token),
|
|
417
384
|
body: JSON.stringify({
|
|
385
|
+
// Keep both casings during the deploy-v1 rollout for the same mixed
|
|
386
|
+
// preview/production route compatibility as the deploy payload above.
|
|
387
|
+
modelProvider: args.modelProvider,
|
|
418
388
|
model_provider: args.modelProvider,
|
|
419
|
-
|
|
420
|
-
|
|
389
|
+
key: args.apiKey,
|
|
390
|
+
api_key: args.apiKey
|
|
421
391
|
})
|
|
422
|
-
}, { action: 'cloud provider credentials
|
|
392
|
+
}, { action: 'cloud BYOK provider credentials' });
|
|
393
|
+
return readCredentialId(body);
|
|
423
394
|
}
|
|
424
395
|
function deriveModelProvider(persona) {
|
|
425
396
|
const model = typeof persona.model === 'string' ? persona.model.trim() : '';
|
|
397
|
+
if (!model)
|
|
398
|
+
return persona.harness;
|
|
399
|
+
const lower = model.toLowerCase();
|
|
400
|
+
if (matchesProviderToken(lower, ['anthropic', 'claude']))
|
|
401
|
+
return 'anthropic';
|
|
402
|
+
if (matchesProviderToken(lower, ['openai', 'codex', 'gpt']))
|
|
403
|
+
return 'openai';
|
|
404
|
+
if (matchesProviderToken(lower, ['google', 'gemini']))
|
|
405
|
+
return 'google';
|
|
406
|
+
if (matchesProviderToken(lower, ['openrouter', 'opencode']))
|
|
407
|
+
return 'openrouter';
|
|
426
408
|
const [provider] = model.split(/[/:]/, 1);
|
|
427
409
|
if (provider?.trim())
|
|
428
|
-
return provider.trim();
|
|
410
|
+
return provider.trim().toLowerCase();
|
|
429
411
|
return persona.harness;
|
|
430
412
|
}
|
|
413
|
+
function matchesProviderToken(model, tokens) {
|
|
414
|
+
return tokens.some((token) => new RegExp(`^${escapeRegExp(token)}($|[/:._-])`).test(model));
|
|
415
|
+
}
|
|
416
|
+
function escapeRegExp(value) {
|
|
417
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
418
|
+
}
|
|
419
|
+
function readCredentialId(body) {
|
|
420
|
+
const direct = readFirstString(body, ['providerCredentialId', 'provider_credential_id', 'credentialId', 'id']);
|
|
421
|
+
if (direct)
|
|
422
|
+
return direct;
|
|
423
|
+
for (const field of ['credential', 'providerCredential']) {
|
|
424
|
+
const nested = body[field];
|
|
425
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
426
|
+
const nestedId = readFirstString(nested, ['id', 'providerCredentialId', 'provider_credential_id']);
|
|
427
|
+
if (nestedId)
|
|
428
|
+
return nestedId;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
throw new Error('cloud provider credentials response missing credential id');
|
|
432
|
+
}
|
|
431
433
|
function providerCredentialsReady(body) {
|
|
432
434
|
const candidates = [
|
|
433
435
|
body.credential,
|
|
@@ -447,87 +449,6 @@ function providerCredentialsReady(body) {
|
|
|
447
449
|
|| typeof record.id === 'string';
|
|
448
450
|
});
|
|
449
451
|
}
|
|
450
|
-
function integrationReady(body, provider) {
|
|
451
|
-
const candidates = [
|
|
452
|
-
...(Array.isArray(body.integrations) ? body.integrations : []),
|
|
453
|
-
body
|
|
454
|
-
];
|
|
455
|
-
return candidates.some((candidate) => {
|
|
456
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
457
|
-
return false;
|
|
458
|
-
const record = candidate;
|
|
459
|
-
const recordProvider = typeof record.provider === 'string' ? record.provider : provider;
|
|
460
|
-
if (recordProvider !== provider)
|
|
461
|
-
return false;
|
|
462
|
-
return record.ready === true
|
|
463
|
-
|| record.state === 'ready'
|
|
464
|
-
|| record.state === 'connected'
|
|
465
|
-
|| typeof record.connectionId === 'string'
|
|
466
|
-
|| typeof record.currentConnectionId === 'string';
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
async function waitForOAuthCallback(args) {
|
|
470
|
-
const state = randomUUID();
|
|
471
|
-
await new Promise((resolve, reject) => {
|
|
472
|
-
let settled = false;
|
|
473
|
-
const timeout = setTimeout(() => {
|
|
474
|
-
settleError(new Error(`timed out waiting for ${args.action} OAuth callback`));
|
|
475
|
-
}, pollTimeoutMs()).unref();
|
|
476
|
-
const server = createServer((request, response) => {
|
|
477
|
-
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
|
|
478
|
-
if (requestUrl.pathname !== '/callback') {
|
|
479
|
-
response.statusCode = 404;
|
|
480
|
-
response.end('not found');
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (requestUrl.searchParams.get('state') !== state) {
|
|
484
|
-
response.statusCode = 400;
|
|
485
|
-
response.end('invalid state');
|
|
486
|
-
settleError(new Error(`${args.action} OAuth callback returned an invalid state`));
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const error = requestUrl.searchParams.get('error');
|
|
490
|
-
if (error) {
|
|
491
|
-
response.statusCode = 400;
|
|
492
|
-
response.end('OAuth failed');
|
|
493
|
-
settleError(new Error(error));
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
response.statusCode = 200;
|
|
497
|
-
response.end('workforce OAuth complete; you can close this tab');
|
|
498
|
-
settleOk();
|
|
499
|
-
});
|
|
500
|
-
function settleOk() {
|
|
501
|
-
if (settled)
|
|
502
|
-
return;
|
|
503
|
-
settled = true;
|
|
504
|
-
clearTimeout(timeout);
|
|
505
|
-
server.close();
|
|
506
|
-
resolve();
|
|
507
|
-
}
|
|
508
|
-
function settleError(error) {
|
|
509
|
-
if (settled)
|
|
510
|
-
return;
|
|
511
|
-
settled = true;
|
|
512
|
-
clearTimeout(timeout);
|
|
513
|
-
server.close();
|
|
514
|
-
reject(error);
|
|
515
|
-
}
|
|
516
|
-
server.on('error', settleError);
|
|
517
|
-
server.listen(0, '127.0.0.1', () => {
|
|
518
|
-
const address = server.address();
|
|
519
|
-
if (!address || typeof address === 'string') {
|
|
520
|
-
settleError(new Error(`failed to start ${args.action} OAuth callback server`));
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
const callback = new URL('/callback', `http://127.0.0.1:${address.port}`);
|
|
524
|
-
callback.searchParams.set('state', state);
|
|
525
|
-
const connectUrl = args.buildUrl(callback.toString());
|
|
526
|
-
args.io.info(`cloud: open ${connectUrl} to finish ${args.action} OAuth`);
|
|
527
|
-
tryOpenBrowser(connectUrl);
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
452
|
function expectHarnessSource(value) {
|
|
532
453
|
const normalized = value.trim().toLowerCase();
|
|
533
454
|
if (normalized === 'plan' || normalized === 'byok' || normalized === 'oauth') {
|
|
@@ -574,9 +495,33 @@ function readPersonaCloudDeployUrl(persona) {
|
|
|
574
495
|
function normalizeCloudUrl(url) {
|
|
575
496
|
const trimmed = url.trim();
|
|
576
497
|
if (!trimmed)
|
|
577
|
-
return
|
|
498
|
+
return normalizeCloudUrl(defaultApiUrl());
|
|
578
499
|
return trimmed.replace(/\/+$/, '');
|
|
579
500
|
}
|
|
501
|
+
async function readUsableCloudAuth(apiUrl) {
|
|
502
|
+
let auth = await cloudCredentialDeps.readStoredAuth().catch(() => null);
|
|
503
|
+
if (!auth)
|
|
504
|
+
return null;
|
|
505
|
+
if (isAuthExpired(auth.accessTokenExpiresAt)) {
|
|
506
|
+
auth = await cloudCredentialDeps.refreshStoredAuth(auth).catch((err) => {
|
|
507
|
+
console.warn(`cloud: stored auth refresh failed: ${formatErrorMessage(err)}`);
|
|
508
|
+
return null;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (!auth)
|
|
512
|
+
return null;
|
|
513
|
+
return {
|
|
514
|
+
...auth,
|
|
515
|
+
apiUrl
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function formatErrorMessage(err) {
|
|
519
|
+
return err instanceof Error ? err.message : String(err);
|
|
520
|
+
}
|
|
521
|
+
function isAuthExpired(expiresAt) {
|
|
522
|
+
const millis = Date.parse(expiresAt);
|
|
523
|
+
return Number.isNaN(millis) || millis <= Date.now() + 60_000;
|
|
524
|
+
}
|
|
580
525
|
function readInputsOverride() {
|
|
581
526
|
const raw = process.env.WORKFORCE_DEPLOY_INPUTS_JSON?.trim();
|
|
582
527
|
if (!raw)
|
|
@@ -678,19 +623,6 @@ function emitLog(args, line) {
|
|
|
678
623
|
args.onLog?.(line);
|
|
679
624
|
args.io.info(line);
|
|
680
625
|
}
|
|
681
|
-
function tryOpenBrowser(url) {
|
|
682
|
-
const command = platform() === 'darwin'
|
|
683
|
-
? 'open'
|
|
684
|
-
: platform() === 'win32'
|
|
685
|
-
? 'cmd'
|
|
686
|
-
: 'xdg-open';
|
|
687
|
-
const args = platform() === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
688
|
-
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
689
|
-
child.on('error', () => {
|
|
690
|
-
// URL is printed; browser launch is best-effort.
|
|
691
|
-
});
|
|
692
|
-
child.unref();
|
|
693
|
-
}
|
|
694
626
|
function pollTimeoutMs() {
|
|
695
627
|
return numberFromEnv('WORKFORCE_DEPLOY_POLL_TIMEOUT_MS') ?? POLL_TIMEOUT_MS;
|
|
696
628
|
}
|