crewly 1.20.144 → 1.20.145
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/config/roles/orchestrator/prompt.md +9 -0
- package/config/skills/orchestrator/harness-login/SKILL.md +88 -0
- package/config/skills/orchestrator/harness-login/execute.sh +75 -0
- package/config/skills/orchestrator/harness-login/execute.test.sh +72 -0
- package/dist/backend/backend/src/constants.d.ts +12 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +12 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/harness/harness.routes.js +3 -0
- package/dist/backend/backend/src/controllers/harness/harness.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts +61 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +133 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +4 -1
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +23 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js +18 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js +210 -20
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts +87 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.js +191 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts +68 -9
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js +121 -11
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js.map +1 -1
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.js +127 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +12 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +12 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +23 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js +18 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js +210 -20
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts +87 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.js +191 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.js.map +1 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.js +127 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
- package/package.json +1 -1
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +39 -0
- package/packages/crewly-agent/src/runtime/agent-runner.service.ts +17 -17
- package/packages/crewly-agent/src/runtime/interactive-login-guard.test.ts +60 -0
- package/packages/crewly-agent/src/runtime/interactive-login-guard.ts +76 -0
- package/packages/crewly-agent/src/runtime/reply-tool-calls.test.ts +53 -0
- package/packages/crewly-agent/src/runtime/reply-tool-calls.ts +76 -0
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +8 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +14 -0
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
* looks like a code), the retry keyword, or a reply to an unrecognised
|
|
18
18
|
* login screen. Everything else goes through the normal chat path.
|
|
19
19
|
* - The broker's `update` / `finished` events.
|
|
20
|
+
* - {@link HarnessReloginService.startOwnerLogin} — the owner asked for a
|
|
21
|
+
* login: 「重新登录 claude」 in the orc DM (parsed by
|
|
22
|
+
* {@link HarnessReloginService.handleOwnerReply}, no LLM involved) or the
|
|
23
|
+
* orchestrator's `harness-login` skill. Such a flow is *forced*: it runs
|
|
24
|
+
* even when the harness is still logged in (switching accounts), skips the
|
|
25
|
+
* silent API-key path, and answers in the conversation it was asked in.
|
|
20
26
|
*
|
|
21
27
|
* Rules:
|
|
22
28
|
* - One flow per harness. Repeated detections only add stuck sessions. A
|
|
@@ -48,16 +54,63 @@ export interface ExpiryReport {
|
|
|
48
54
|
*/
|
|
49
55
|
source: ExpirySource;
|
|
50
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Where an owner-requested login answers: the Slack conversation (and thread)
|
|
59
|
+
* the owner asked in. `agentSession` is set when that conversation is a DM
|
|
60
|
+
* with an agent's own bot (the orc's "Crewly Orc" app), whose token must be
|
|
61
|
+
* used to post there.
|
|
62
|
+
*/
|
|
63
|
+
export interface ReloginReplyTarget {
|
|
64
|
+
channelId: string;
|
|
65
|
+
threadTs?: string;
|
|
66
|
+
agentSession?: string;
|
|
67
|
+
}
|
|
51
68
|
/** Sends a Slack DM to the owner. */
|
|
52
69
|
export interface ReloginOwnerNotifier {
|
|
53
70
|
/**
|
|
54
71
|
* Send a DM to the owner.
|
|
55
72
|
*
|
|
56
73
|
* @param text - Slack mrkdwn text (never contains a secret)
|
|
74
|
+
* @param target - Conversation to answer in; absent = the owner's DM with the master bot
|
|
57
75
|
* @returns True when delivered
|
|
58
76
|
*/
|
|
59
|
-
sendToOwner(text: string): Promise<boolean>;
|
|
77
|
+
sendToOwner(text: string, target?: ReloginReplyTarget | null): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Whether a DM can be sent right now (Slack connected).
|
|
80
|
+
*
|
|
81
|
+
* @returns True when {@link sendToOwner} can deliver
|
|
82
|
+
*/
|
|
83
|
+
isAvailable?(): boolean;
|
|
84
|
+
}
|
|
85
|
+
/** What started a flow. */
|
|
86
|
+
export type ReloginTrigger = 'expiry' | 'owner';
|
|
87
|
+
/** Options of an owner-requested login. */
|
|
88
|
+
export interface OwnerLoginOptions {
|
|
89
|
+
/** The owner wants a different account (only changes the wording) */
|
|
90
|
+
switchAccount?: boolean;
|
|
91
|
+
/** Conversation to answer in (null = the owner's master-bot DM) */
|
|
92
|
+
replyTarget?: ReloginReplyTarget | null;
|
|
93
|
+
/** Who relayed the request, for logs */
|
|
94
|
+
requestedBy: 'owner_dm' | 'orchestrator';
|
|
60
95
|
}
|
|
96
|
+
/** An owner-requested login that was started. */
|
|
97
|
+
export interface OwnerLoginStarted {
|
|
98
|
+
/** `restarted`: a flow for the harness was running and was started over */
|
|
99
|
+
status: 'started' | 'restarted';
|
|
100
|
+
harnessId: HarnessId;
|
|
101
|
+
/** Whether the owner can be DM'd right now (false = Slack is down) */
|
|
102
|
+
dmAvailable: boolean;
|
|
103
|
+
}
|
|
104
|
+
/** An owner-requested login for a harness without a link login. */
|
|
105
|
+
export interface OwnerLoginUnsupported {
|
|
106
|
+
/** The harness has no link login (Antigravity: API key; Gemini: enterprise only) */
|
|
107
|
+
status: 'no_broker_login';
|
|
108
|
+
harnessId: HarnessId;
|
|
109
|
+
/** What to tell the owner, in Chinese */
|
|
110
|
+
message: string;
|
|
111
|
+
}
|
|
112
|
+
/** Result of {@link HarnessReloginService.startOwnerLogin}. */
|
|
113
|
+
export type OwnerLoginResult = OwnerLoginStarted | OwnerLoginUnsupported;
|
|
61
114
|
/** Restarts agents after a login succeeded. */
|
|
62
115
|
export interface ReloginAgentResumer {
|
|
63
116
|
/**
|
|
@@ -136,22 +189,31 @@ export declare const SCREEN_REPLY_PREFIXES: readonly ["输入", "input"];
|
|
|
136
189
|
* @returns Text to type, or null when the reply has no prefix
|
|
137
190
|
*/
|
|
138
191
|
export declare function stripScreenReplyPrefix(reply: string): string | null;
|
|
192
|
+
/** How a DM introduces the login. */
|
|
193
|
+
export interface LoginDmWording {
|
|
194
|
+
/** The owner asked for this login (not an expiry) */
|
|
195
|
+
ownerRequested?: boolean;
|
|
196
|
+
/** The owner asked for a different account */
|
|
197
|
+
switchAccount?: boolean;
|
|
198
|
+
}
|
|
139
199
|
/**
|
|
140
200
|
* DM with the sign-in link (and Codex's one-time code on its own line).
|
|
141
201
|
*
|
|
142
202
|
* @param session - Broker session (never contains a secret)
|
|
143
203
|
* @param waiting - Agents waiting on the login
|
|
204
|
+
* @param wording - Owner-requested / account-switch wording (default: expiry)
|
|
144
205
|
* @returns Slack mrkdwn text
|
|
145
206
|
*/
|
|
146
|
-
export declare function formatLinkDm(session: LoginSession, waiting: readonly string[]): string;
|
|
207
|
+
export declare function formatLinkDm(session: LoginSession, waiting: readonly string[], wording?: LoginDmWording): string;
|
|
147
208
|
/**
|
|
148
209
|
* DM for a login screen the broker did not recognise.
|
|
149
210
|
*
|
|
150
211
|
* @param session - Broker session
|
|
151
212
|
* @param waiting - Agents waiting on the login
|
|
213
|
+
* @param wording - Owner-requested / account-switch wording (default: expiry)
|
|
152
214
|
* @returns Slack mrkdwn text
|
|
153
215
|
*/
|
|
154
|
-
export declare function formatScreenDm(session: LoginSession, waiting: readonly string[]): string;
|
|
216
|
+
export declare function formatScreenDm(session: LoginSession, waiting: readonly string[], wording?: LoginDmWording): string;
|
|
155
217
|
/**
|
|
156
218
|
* DM after the harness rejected the owner's reply (e.g. a wrong code).
|
|
157
219
|
*
|
|
@@ -170,6 +232,31 @@ export declare function formatSuccessDm(harnessId: HarnessId, result: {
|
|
|
170
232
|
resumed: string[];
|
|
171
233
|
failed: string[];
|
|
172
234
|
}): string;
|
|
235
|
+
/**
|
|
236
|
+
* One-line DM after an owner-requested login succeeded.
|
|
237
|
+
*
|
|
238
|
+
* @param harnessId - Harness
|
|
239
|
+
* @param result - Which agents were restarted onto the new login
|
|
240
|
+
* @returns Slack mrkdwn text
|
|
241
|
+
*/
|
|
242
|
+
export declare function formatOwnerSuccessDm(harnessId: HarnessId, result: {
|
|
243
|
+
resumed: string[];
|
|
244
|
+
failed: string[];
|
|
245
|
+
}): string;
|
|
246
|
+
/**
|
|
247
|
+
* Reply to a login request that named no harness Crewly can log in.
|
|
248
|
+
*
|
|
249
|
+
* @param name - What the owner wrote, or null when they named none
|
|
250
|
+
* @returns Slack mrkdwn text
|
|
251
|
+
*/
|
|
252
|
+
export declare function formatWhichHarnessDm(name: string | null): string;
|
|
253
|
+
/**
|
|
254
|
+
* Reply for a harness that has no link login.
|
|
255
|
+
*
|
|
256
|
+
* @param harnessId - Harness without a broker login method
|
|
257
|
+
* @returns Slack mrkdwn text
|
|
258
|
+
*/
|
|
259
|
+
export declare function formatNoBrokerLoginDm(harnessId: HarnessId): string;
|
|
173
260
|
/**
|
|
174
261
|
* DM after the login failed or timed out (sent once per failure).
|
|
175
262
|
*
|
|
@@ -226,12 +313,45 @@ export declare class HarnessReloginService {
|
|
|
226
313
|
*/
|
|
227
314
|
reportExpiry(report: ExpiryReport): boolean;
|
|
228
315
|
/**
|
|
229
|
-
* Offer an owner's Slack DM
|
|
316
|
+
* Offer an owner's Slack DM to the re-login coordinator.
|
|
317
|
+
*
|
|
318
|
+
* Consumed, in this order: the retry keyword (while a flow exists), a
|
|
319
|
+
* login code or screen reply for a running flow, and finally an owner
|
|
320
|
+
* login request (「重新登录 claude」, "relogin codex" — see
|
|
321
|
+
* {@link parseOwnerLoginRequest}), which starts a forced login answered in
|
|
322
|
+
* `target`.
|
|
230
323
|
*
|
|
231
324
|
* @param text - Reply text (never logged)
|
|
325
|
+
* @param target - Conversation the owner wrote in (answers go there)
|
|
232
326
|
* @returns True when consumed; the caller must then NOT pass it on as a chat message
|
|
233
327
|
*/
|
|
234
|
-
handleOwnerReply(text: string): boolean;
|
|
328
|
+
handleOwnerReply(text: string, target?: ReloginReplyTarget | null): boolean;
|
|
329
|
+
/**
|
|
330
|
+
* Start a login the owner asked for, through the broker: forced (runs
|
|
331
|
+
* even when the harness is still logged in — the owner may be switching
|
|
332
|
+
* accounts), no silent API key, answered in `replyTarget`. A flow already
|
|
333
|
+
* running for the harness is started over with a fresh link.
|
|
334
|
+
*
|
|
335
|
+
* @param harnessId - Harness to log in
|
|
336
|
+
* @param options - Account switch, reply target, who relayed the request
|
|
337
|
+
* @returns What happened (never throws)
|
|
338
|
+
*/
|
|
339
|
+
startOwnerLogin(harnessId: HarnessId, options: OwnerLoginOptions): OwnerLoginResult;
|
|
340
|
+
/**
|
|
341
|
+
* Act on a parsed owner login request from the DM.
|
|
342
|
+
*
|
|
343
|
+
* @param request - Parsed request
|
|
344
|
+
* @param target - Conversation to answer in
|
|
345
|
+
*/
|
|
346
|
+
private handleOwnerLoginRequest;
|
|
347
|
+
/**
|
|
348
|
+
* Offer a DM to the running flows: the retry keyword, a login code, or a
|
|
349
|
+
* reply to an unrecognised screen.
|
|
350
|
+
*
|
|
351
|
+
* @param text - Reply text (never logged)
|
|
352
|
+
* @returns True when consumed
|
|
353
|
+
*/
|
|
354
|
+
private consumeFlowReply;
|
|
235
355
|
/**
|
|
236
356
|
* The pending re-login for a harness (shown by `GET /api/harness`).
|
|
237
357
|
*
|
|
@@ -261,6 +381,7 @@ export declare class HarnessReloginService {
|
|
|
261
381
|
*
|
|
262
382
|
* @param harnessId - Harness
|
|
263
383
|
* @param stuck - Sessions known to be stuck
|
|
384
|
+
* @param options - Trigger, wording and reply target (default: a detected expiry)
|
|
264
385
|
*/
|
|
265
386
|
private begin;
|
|
266
387
|
/**
|
|
@@ -269,6 +390,12 @@ export declare class HarnessReloginService {
|
|
|
269
390
|
* @param flow - The flow
|
|
270
391
|
*/
|
|
271
392
|
private run;
|
|
393
|
+
/**
|
|
394
|
+
* Start (or adopt) the harness's broker login session for a flow.
|
|
395
|
+
*
|
|
396
|
+
* @param flow - The flow
|
|
397
|
+
*/
|
|
398
|
+
private startBrokerSession;
|
|
272
399
|
/**
|
|
273
400
|
* Use an API key Crewly holds for the harness, without asking the owner.
|
|
274
401
|
* Not repeated within SILENT_KEY_RETRY_WINDOW_MS: a key that did not
|
|
@@ -303,6 +430,13 @@ export declare class HarnessReloginService {
|
|
|
303
430
|
* @param flow - The flow to replace
|
|
304
431
|
*/
|
|
305
432
|
private restart;
|
|
433
|
+
/**
|
|
434
|
+
* DM wording for a flow.
|
|
435
|
+
*
|
|
436
|
+
* @param flow - The flow
|
|
437
|
+
* @returns Owner-requested / account-switch flags
|
|
438
|
+
*/
|
|
439
|
+
private wordingOf;
|
|
306
440
|
/**
|
|
307
441
|
* Cancel the flow's live broker session, marking it as cancelled by the
|
|
308
442
|
* coordinator so no failure DM is sent for it.
|
|
@@ -330,6 +464,14 @@ export declare class HarnessReloginService {
|
|
|
330
464
|
* @param text - DM text
|
|
331
465
|
*/
|
|
332
466
|
private dm;
|
|
467
|
+
/**
|
|
468
|
+
* Send a message to the owner (logs when no notifier is wired; never logs the text).
|
|
469
|
+
*
|
|
470
|
+
* @param text - DM text
|
|
471
|
+
* @param target - Conversation to answer in (null = master-bot DM)
|
|
472
|
+
* @param harnessId - Harness, for logs
|
|
473
|
+
*/
|
|
474
|
+
private notify;
|
|
333
475
|
/**
|
|
334
476
|
* Read a broker session.
|
|
335
477
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-relogin.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-relogin.service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"harness-relogin.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-relogin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
* looks like a code), the retry keyword, or a reply to an unrecognised
|
|
18
18
|
* login screen. Everything else goes through the normal chat path.
|
|
19
19
|
* - The broker's `update` / `finished` events.
|
|
20
|
+
* - {@link HarnessReloginService.startOwnerLogin} — the owner asked for a
|
|
21
|
+
* login: 「重新登录 claude」 in the orc DM (parsed by
|
|
22
|
+
* {@link HarnessReloginService.handleOwnerReply}, no LLM involved) or the
|
|
23
|
+
* orchestrator's `harness-login` skill. Such a flow is *forced*: it runs
|
|
24
|
+
* even when the harness is still logged in (switching accounts), skips the
|
|
25
|
+
* silent API-key path, and answers in the conversation it was asked in.
|
|
20
26
|
*
|
|
21
27
|
* Rules:
|
|
22
28
|
* - One flow per harness. Repeated detections only add stuck sessions. A
|
|
@@ -39,6 +45,9 @@ import { getHarnessService } from './harness.service.js';
|
|
|
39
45
|
import { SILENT_HARNESS_LOGGER, isHarnessId, isTerminalLoginState, } from './harness.types.js';
|
|
40
46
|
import { LOGIN_BROKER_EVENTS } from './login-broker.service.js';
|
|
41
47
|
import { redactSecrets } from './login-rules.js';
|
|
48
|
+
import { parseOwnerLoginRequest } from './owner-login-request.js';
|
|
49
|
+
/** A detected expiry: not forced, standard wording, master-bot DM. */
|
|
50
|
+
const EXPIRY_FLOW = { trigger: 'expiry', switchAccount: false, replyTarget: null };
|
|
42
51
|
/** Harnesses the coordinator can log in (they have a broker method). */
|
|
43
52
|
function hasBrokerLogin(harnessId) {
|
|
44
53
|
return getBrokerLoginMethod(harnessId) !== undefined;
|
|
@@ -129,22 +138,40 @@ export function stripScreenReplyPrefix(reply) {
|
|
|
129
138
|
}
|
|
130
139
|
return null;
|
|
131
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* First line of a login DM.
|
|
143
|
+
*
|
|
144
|
+
* @param harnessId - Harness
|
|
145
|
+
* @param waiting - Agents waiting on the login (expiry wording only)
|
|
146
|
+
* @param wording - Owner-requested / account switch
|
|
147
|
+
* @returns Slack mrkdwn line
|
|
148
|
+
*/
|
|
149
|
+
function loginDmHeader(harnessId, waiting, wording) {
|
|
150
|
+
const name = displayName(harnessId);
|
|
151
|
+
if (!wording.ownerRequested)
|
|
152
|
+
return `*${name} 登录过期了。* ${describeWaitingAgents(waiting)}`;
|
|
153
|
+
return wording.switchAccount ? `*换账号登录 ${name}*` : `*登录 ${name}*`;
|
|
154
|
+
}
|
|
132
155
|
/**
|
|
133
156
|
* DM with the sign-in link (and Codex's one-time code on its own line).
|
|
134
157
|
*
|
|
135
158
|
* @param session - Broker session (never contains a secret)
|
|
136
159
|
* @param waiting - Agents waiting on the login
|
|
160
|
+
* @param wording - Owner-requested / account-switch wording (default: expiry)
|
|
137
161
|
* @returns Slack mrkdwn text
|
|
138
162
|
*/
|
|
139
|
-
export function formatLinkDm(session, waiting) {
|
|
140
|
-
const
|
|
141
|
-
const lines = [`*${name} 登录过期了。* ${describeWaitingAgents(waiting)}`, ''];
|
|
163
|
+
export function formatLinkDm(session, waiting, wording = {}) {
|
|
164
|
+
const lines = [loginDmHeader(session.harnessId, waiting, wording), ''];
|
|
142
165
|
if (session.method === 'device') {
|
|
143
166
|
lines.push('1. 在手机上打开这个链接:', session.url ?? '', '', '2. 输入这个验证码:', session.userCode ?? '', '');
|
|
167
|
+
if (wording.switchAccount)
|
|
168
|
+
lines.push('要换账号的话,先在打开的页面里切到要用的那个账号。');
|
|
144
169
|
lines.push('在手机上完成登录就行,这边会自动继续。');
|
|
145
170
|
}
|
|
146
171
|
else {
|
|
147
172
|
lines.push('1. 在手机上打开这个链接并授权:', session.url ?? '', '');
|
|
173
|
+
if (wording.switchAccount)
|
|
174
|
+
lines.push(' 要换账号的话,先在打开的页面里切到要用的那个账号再授权。', '');
|
|
148
175
|
lines.push('2. 把授权后页面显示的代码直接回复在这里(只要代码)。', '');
|
|
149
176
|
lines.push('Crewly 会自动填进去,agent 自己接着干。');
|
|
150
177
|
}
|
|
@@ -155,12 +182,13 @@ export function formatLinkDm(session, waiting) {
|
|
|
155
182
|
*
|
|
156
183
|
* @param session - Broker session
|
|
157
184
|
* @param waiting - Agents waiting on the login
|
|
185
|
+
* @param wording - Owner-requested / account-switch wording (default: expiry)
|
|
158
186
|
* @returns Slack mrkdwn text
|
|
159
187
|
*/
|
|
160
|
-
export function formatScreenDm(session, waiting) {
|
|
188
|
+
export function formatScreenDm(session, waiting, wording = {}) {
|
|
161
189
|
const screen = safeForDm(session.screen, HARNESS_CONSTANTS.RELOGIN.DM_SCREEN_MAX_CHARS) || '(empty screen)';
|
|
162
190
|
return [
|
|
163
|
-
|
|
191
|
+
loginDmHeader(session.harnessId, waiting, wording),
|
|
164
192
|
'',
|
|
165
193
|
'Crewly 已经启动了登录,但没认出它的界面。现在显示的是:',
|
|
166
194
|
'```',
|
|
@@ -195,6 +223,48 @@ export function formatSuccessDm(harnessId, result) {
|
|
|
195
223
|
}
|
|
196
224
|
return text;
|
|
197
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* One-line DM after an owner-requested login succeeded.
|
|
228
|
+
*
|
|
229
|
+
* @param harnessId - Harness
|
|
230
|
+
* @param result - Which agents were restarted onto the new login
|
|
231
|
+
* @returns Slack mrkdwn text
|
|
232
|
+
*/
|
|
233
|
+
export function formatOwnerSuccessDm(harnessId, result) {
|
|
234
|
+
let text = `好了:${displayName(harnessId)} 已登录。`;
|
|
235
|
+
if (result.resumed.length > 0)
|
|
236
|
+
text += ` ${result.resumed.length} 个 agent 已重启,用上了新登录。`;
|
|
237
|
+
if (result.failed.length > 0)
|
|
238
|
+
text += ` 没能重启:${result.failed.join('、')}。`;
|
|
239
|
+
return text;
|
|
240
|
+
}
|
|
241
|
+
/** Harness names the owner can type, for the "which one?" reply. */
|
|
242
|
+
const WHICH_HARNESS_HINT = '回复「重新登录 claude」或「重新登录 codex」(要换账号就说「换个账号登录 claude」)。';
|
|
243
|
+
/**
|
|
244
|
+
* Reply to a login request that named no harness Crewly can log in.
|
|
245
|
+
*
|
|
246
|
+
* @param name - What the owner wrote, or null when they named none
|
|
247
|
+
* @returns Slack mrkdwn text
|
|
248
|
+
*/
|
|
249
|
+
export function formatWhichHarnessDm(name) {
|
|
250
|
+
return name ? `Crewly 没有「${name}」这个编程助手。要登录哪个?${WHICH_HARNESS_HINT}` : `要登录哪个?${WHICH_HARNESS_HINT}`;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Reply for a harness that has no link login.
|
|
254
|
+
*
|
|
255
|
+
* @param harnessId - Harness without a broker login method
|
|
256
|
+
* @returns Slack mrkdwn text
|
|
257
|
+
*/
|
|
258
|
+
export function formatNoBrokerLoginDm(harnessId) {
|
|
259
|
+
const name = displayName(harnessId);
|
|
260
|
+
if (harnessId === HARNESS_CONSTANTS.IDS.ANTIGRAVITY_CLI) {
|
|
261
|
+
return `${name} 用的是 Gemini API key,不走链接登录。在电脑上运行 \`crewly login antigravity\`,或者在 Crewly 设置页(Setup → 登录)填 key。`;
|
|
262
|
+
}
|
|
263
|
+
if (harnessId === HARNESS_CONSTANTS.IDS.GEMINI_CLI) {
|
|
264
|
+
return `${name} 只给企业版用,Crewly 没法帮它登录。${WHICH_HARNESS_HINT}`;
|
|
265
|
+
}
|
|
266
|
+
return `${name} 没有链接登录,Crewly 帮不了它。${WHICH_HARNESS_HINT}`;
|
|
267
|
+
}
|
|
198
268
|
/**
|
|
199
269
|
* DM after the login failed or timed out (sent once per failure).
|
|
200
270
|
*
|
|
@@ -299,14 +369,91 @@ export class HarnessReloginService {
|
|
|
299
369
|
return true;
|
|
300
370
|
}
|
|
301
371
|
/**
|
|
302
|
-
* Offer an owner's Slack DM
|
|
372
|
+
* Offer an owner's Slack DM to the re-login coordinator.
|
|
373
|
+
*
|
|
374
|
+
* Consumed, in this order: the retry keyword (while a flow exists), a
|
|
375
|
+
* login code or screen reply for a running flow, and finally an owner
|
|
376
|
+
* login request (「重新登录 claude」, "relogin codex" — see
|
|
377
|
+
* {@link parseOwnerLoginRequest}), which starts a forced login answered in
|
|
378
|
+
* `target`.
|
|
303
379
|
*
|
|
304
380
|
* @param text - Reply text (never logged)
|
|
381
|
+
* @param target - Conversation the owner wrote in (answers go there)
|
|
305
382
|
* @returns True when consumed; the caller must then NOT pass it on as a chat message
|
|
306
383
|
*/
|
|
307
|
-
handleOwnerReply(text) {
|
|
308
|
-
if (typeof text !== 'string'
|
|
384
|
+
handleOwnerReply(text, target = null) {
|
|
385
|
+
if (typeof text !== 'string')
|
|
386
|
+
return false;
|
|
387
|
+
if (this.flows.size > 0 && this.consumeFlowReply(text))
|
|
388
|
+
return true;
|
|
389
|
+
const request = parseOwnerLoginRequest(text);
|
|
390
|
+
if (!request)
|
|
309
391
|
return false;
|
|
392
|
+
void this.handleOwnerLoginRequest(request, target);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Start a login the owner asked for, through the broker: forced (runs
|
|
397
|
+
* even when the harness is still logged in — the owner may be switching
|
|
398
|
+
* accounts), no silent API key, answered in `replyTarget`. A flow already
|
|
399
|
+
* running for the harness is started over with a fresh link.
|
|
400
|
+
*
|
|
401
|
+
* @param harnessId - Harness to log in
|
|
402
|
+
* @param options - Account switch, reply target, who relayed the request
|
|
403
|
+
* @returns What happened (never throws)
|
|
404
|
+
*/
|
|
405
|
+
startOwnerLogin(harnessId, options) {
|
|
406
|
+
if (!hasBrokerLogin(harnessId)) {
|
|
407
|
+
return { status: 'no_broker_login', harnessId, message: formatNoBrokerLoginDm(harnessId) };
|
|
408
|
+
}
|
|
409
|
+
const existing = this.flows.get(harnessId);
|
|
410
|
+
const stuck = existing ? new Set(existing.stuck) : new Set();
|
|
411
|
+
if (existing) {
|
|
412
|
+
this.clearScreenTimer(existing);
|
|
413
|
+
this.cancelOwnSession(existing);
|
|
414
|
+
if (this.flows.get(harnessId) === existing)
|
|
415
|
+
this.flows.delete(harnessId);
|
|
416
|
+
}
|
|
417
|
+
// The owner asked: a quiet period after the last login must not swallow it.
|
|
418
|
+
this.quietUntil.delete(harnessId);
|
|
419
|
+
this.logger.info('Owner-requested login started', {
|
|
420
|
+
harnessId,
|
|
421
|
+
requestedBy: options.requestedBy,
|
|
422
|
+
switchAccount: options.switchAccount === true,
|
|
423
|
+
restarted: Boolean(existing),
|
|
424
|
+
answersInThread: Boolean(options.replyTarget),
|
|
425
|
+
});
|
|
426
|
+
this.begin(harnessId, stuck, {
|
|
427
|
+
trigger: 'owner',
|
|
428
|
+
switchAccount: options.switchAccount === true,
|
|
429
|
+
replyTarget: options.replyTarget ?? null,
|
|
430
|
+
});
|
|
431
|
+
const dmAvailable = this.notifier ? (this.notifier.isAvailable?.() ?? true) : false;
|
|
432
|
+
return { status: existing ? 'restarted' : 'started', harnessId, dmAvailable };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Act on a parsed owner login request from the DM.
|
|
436
|
+
*
|
|
437
|
+
* @param request - Parsed request
|
|
438
|
+
* @param target - Conversation to answer in
|
|
439
|
+
*/
|
|
440
|
+
async handleOwnerLoginRequest(request, target) {
|
|
441
|
+
if (request.kind === 'unknown') {
|
|
442
|
+
await this.notify(formatWhichHarnessDm(request.name), target);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const result = this.startOwnerLogin(request.harnessId, { switchAccount: request.switchAccount, replyTarget: target, requestedBy: 'owner_dm' });
|
|
446
|
+
if (result.status === 'no_broker_login')
|
|
447
|
+
await this.notify(result.message, target);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Offer a DM to the running flows: the retry keyword, a login code, or a
|
|
451
|
+
* reply to an unrecognised screen.
|
|
452
|
+
*
|
|
453
|
+
* @param text - Reply text (never logged)
|
|
454
|
+
* @returns True when consumed
|
|
455
|
+
*/
|
|
456
|
+
consumeFlowReply(text) {
|
|
310
457
|
if (isRetryKeyword(text)) {
|
|
311
458
|
const flows = [...this.flows.values()];
|
|
312
459
|
const failed = flows.filter((flow) => flow.phase === 'failed');
|
|
@@ -420,8 +567,9 @@ export class HarnessReloginService {
|
|
|
420
567
|
*
|
|
421
568
|
* @param harnessId - Harness
|
|
422
569
|
* @param stuck - Sessions known to be stuck
|
|
570
|
+
* @param options - Trigger, wording and reply target (default: a detected expiry)
|
|
423
571
|
*/
|
|
424
|
-
begin(harnessId, stuck) {
|
|
572
|
+
begin(harnessId, stuck, options = EXPIRY_FLOW) {
|
|
425
573
|
const flow = {
|
|
426
574
|
harnessId,
|
|
427
575
|
phase: 'running',
|
|
@@ -434,6 +582,9 @@ export class HarnessReloginService {
|
|
|
434
582
|
lastRejection: null,
|
|
435
583
|
screenTimer: null,
|
|
436
584
|
cancelledByUs: false,
|
|
585
|
+
trigger: options.trigger,
|
|
586
|
+
switchAccount: options.switchAccount,
|
|
587
|
+
replyTarget: options.replyTarget,
|
|
437
588
|
};
|
|
438
589
|
// Registered synchronously: this is the per-harness debounce.
|
|
439
590
|
this.flows.set(harnessId, flow);
|
|
@@ -445,6 +596,12 @@ export class HarnessReloginService {
|
|
|
445
596
|
* @param flow - The flow
|
|
446
597
|
*/
|
|
447
598
|
async run(flow) {
|
|
599
|
+
// The owner asked for this login (possibly to switch accounts): no
|
|
600
|
+
// "still logged in" check and no silent API key — they want the link.
|
|
601
|
+
if (flow.trigger === 'owner') {
|
|
602
|
+
this.startBrokerSession(flow);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
448
605
|
// An agent's "401" is not proof the login expired — a bad API key or
|
|
449
606
|
// text typed into the sign-in screen gives one too. Starting a login
|
|
450
607
|
// then *logs the harness out* (`codex login` clears its credentials):
|
|
@@ -464,6 +621,14 @@ export class HarnessReloginService {
|
|
|
464
621
|
return;
|
|
465
622
|
if (this.flows.get(flow.harnessId) !== flow)
|
|
466
623
|
return;
|
|
624
|
+
this.startBrokerSession(flow);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Start (or adopt) the harness's broker login session for a flow.
|
|
628
|
+
*
|
|
629
|
+
* @param flow - The flow
|
|
630
|
+
*/
|
|
631
|
+
startBrokerSession(flow) {
|
|
467
632
|
const method = getBrokerLoginMethod(flow.harnessId);
|
|
468
633
|
if (!method)
|
|
469
634
|
return;
|
|
@@ -475,14 +640,14 @@ export class HarnessReloginService {
|
|
|
475
640
|
const reason = error instanceof Error ? error.message : String(error);
|
|
476
641
|
this.logger.warn('Re-login: could not start the login', { harnessId: flow.harnessId, error: reason });
|
|
477
642
|
flow.phase = 'failed';
|
|
478
|
-
|
|
643
|
+
void this.dm(flow, formatFailureDm(flow.harnessId, `Crewly 没能启动登录(${reason})`));
|
|
479
644
|
return;
|
|
480
645
|
}
|
|
481
646
|
flow.sessionId = session.id;
|
|
482
647
|
flow.startedAt = Date.parse(session.startedAt) || this.now();
|
|
483
648
|
flow.screenTimer = setTimeout(() => void this.sendScreenIfUnrecognised(flow), HARNESS_CONSTANTS.RELOGIN.UNRECOGNISED_SCREEN_MS);
|
|
484
649
|
flow.screenTimer.unref?.();
|
|
485
|
-
this.logger.info('Re-login broker session started', { harnessId: flow.harnessId, sessionId: session.id, method: method.id });
|
|
650
|
+
this.logger.info('Re-login broker session started', { harnessId: flow.harnessId, sessionId: session.id, method: method.id, trigger: flow.trigger });
|
|
486
651
|
// The broker may hand back a session that already shows its link.
|
|
487
652
|
if (isTerminalLoginState(session.state))
|
|
488
653
|
this.handleFinished(session);
|
|
@@ -543,7 +708,7 @@ export class HarnessReloginService {
|
|
|
543
708
|
if (flow.dm !== 'link' && linkReady) {
|
|
544
709
|
flow.dm = 'link';
|
|
545
710
|
this.clearScreenTimer(flow);
|
|
546
|
-
void this.dm(flow, formatLinkDm(session, this.waitingAgents(flow)));
|
|
711
|
+
void this.dm(flow, formatLinkDm(session, this.waitingAgents(flow), this.wordingOf(flow)));
|
|
547
712
|
return;
|
|
548
713
|
}
|
|
549
714
|
if (flow.replied && session.state === 'awaiting_user' && session.message && session.message !== flow.lastRejection) {
|
|
@@ -613,8 +778,10 @@ export class HarnessReloginService {
|
|
|
613
778
|
for (const name of result.resumed)
|
|
614
779
|
this.mutedScreens.add(name);
|
|
615
780
|
this.logger.info('Re-login finished', { harnessId: flow.harnessId, resumed: result.resumed.length, failed: result.failed.length });
|
|
616
|
-
if (notify)
|
|
617
|
-
|
|
781
|
+
if (notify) {
|
|
782
|
+
const text = flow.trigger === 'owner' ? formatOwnerSuccessDm(flow.harnessId, result) : formatSuccessDm(flow.harnessId, result);
|
|
783
|
+
await this.dm(flow, text);
|
|
784
|
+
}
|
|
618
785
|
}
|
|
619
786
|
/**
|
|
620
787
|
* Start a flow over (retry keyword or re-reminder).
|
|
@@ -626,7 +793,20 @@ export class HarnessReloginService {
|
|
|
626
793
|
this.cancelOwnSession(flow);
|
|
627
794
|
if (this.flows.get(flow.harnessId) === flow)
|
|
628
795
|
this.flows.delete(flow.harnessId);
|
|
629
|
-
this.begin(flow.harnessId, new Set(flow.stuck)
|
|
796
|
+
this.begin(flow.harnessId, new Set(flow.stuck), {
|
|
797
|
+
trigger: flow.trigger,
|
|
798
|
+
switchAccount: flow.switchAccount,
|
|
799
|
+
replyTarget: flow.replyTarget,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* DM wording for a flow.
|
|
804
|
+
*
|
|
805
|
+
* @param flow - The flow
|
|
806
|
+
* @returns Owner-requested / account-switch flags
|
|
807
|
+
*/
|
|
808
|
+
wordingOf(flow) {
|
|
809
|
+
return { ownerRequested: flow.trigger === 'owner', switchAccount: flow.switchAccount };
|
|
630
810
|
}
|
|
631
811
|
/**
|
|
632
812
|
* Cancel the flow's live broker session, marking it as cancelled by the
|
|
@@ -659,7 +839,7 @@ export class HarnessReloginService {
|
|
|
659
839
|
return;
|
|
660
840
|
flow.dm = 'screen';
|
|
661
841
|
this.logger.warn('Re-login: login screen not recognised, sending it to the owner', { harnessId: flow.harnessId, sessionId: session.id });
|
|
662
|
-
await this.dm(flow, formatScreenDm(session, this.waitingAgents(flow)));
|
|
842
|
+
await this.dm(flow, formatScreenDm(session, this.waitingAgents(flow), this.wordingOf(flow)));
|
|
663
843
|
}
|
|
664
844
|
/**
|
|
665
845
|
* Agents waiting on a flow: the stuck ones, else every session of the harness.
|
|
@@ -685,18 +865,28 @@ export class HarnessReloginService {
|
|
|
685
865
|
*/
|
|
686
866
|
async dm(flow, text) {
|
|
687
867
|
flow.lastDmAt = this.now();
|
|
868
|
+
await this.notify(text, flow.replyTarget, flow.harnessId);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Send a message to the owner (logs when no notifier is wired; never logs the text).
|
|
872
|
+
*
|
|
873
|
+
* @param text - DM text
|
|
874
|
+
* @param target - Conversation to answer in (null = master-bot DM)
|
|
875
|
+
* @param harnessId - Harness, for logs
|
|
876
|
+
*/
|
|
877
|
+
async notify(text, target, harnessId) {
|
|
688
878
|
if (!this.notifier) {
|
|
689
|
-
this.logger.warn('Re-login: no Slack DM path to the owner; finish the login from Setup', { harnessId
|
|
879
|
+
this.logger.warn('Re-login: no Slack DM path to the owner; finish the login from Setup', { harnessId });
|
|
690
880
|
return;
|
|
691
881
|
}
|
|
692
882
|
try {
|
|
693
|
-
const delivered = await this.notifier.sendToOwner(text);
|
|
883
|
+
const delivered = await this.notifier.sendToOwner(text, target);
|
|
694
884
|
if (!delivered)
|
|
695
|
-
this.logger.warn('Re-login: the owner DM was not delivered', { harnessId
|
|
885
|
+
this.logger.warn('Re-login: the owner DM was not delivered', { harnessId });
|
|
696
886
|
}
|
|
697
887
|
catch (error) {
|
|
698
888
|
this.logger.warn('Re-login: sending the owner DM failed', {
|
|
699
|
-
harnessId
|
|
889
|
+
harnessId,
|
|
700
890
|
error: error instanceof Error ? error.message : String(error),
|
|
701
891
|
});
|
|
702
892
|
}
|