crewly 1.20.180 → 1.20.181
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/skills/agent/remote-browser/SKILL.md +28 -8
- package/dist/backend/backend/src/constants.d.ts +23 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +24 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +2 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +15 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +47 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts +202 -0
- package/dist/backend/backend/src/services/browser/browser-approval.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-approval.service.js +533 -0
- package/dist/backend/backend/src/services/browser/browser-approval.service.js.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +24 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js +55 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +51 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +82 -3
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/held-action-store.d.ts +125 -0
- package/dist/backend/backend/src/services/browser/held-action-store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/held-action-store.js +141 -0
- package/dist/backend/backend/src/services/browser/held-action-store.js.map +1 -0
- package/dist/backend/backend/src/services/decisions/decision-card.d.ts +13 -5
- package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-card.js +35 -9
- package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +58 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +94 -4
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/types/decision.types.d.ts +31 -2
- package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +23 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +24 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner approval of held browser actions.
|
|
3
|
+
*
|
|
4
|
+
* When `BrowserSessionService.authorize` holds an irreversible click (send,
|
|
5
|
+
* submit, pay, delete…), this service:
|
|
6
|
+
*
|
|
7
|
+
* 1. persists the hold under CREWLY_HOME ({@link HeldActionStore});
|
|
8
|
+
* 2. asks the owner with a Slack decision card (kind `browser_action`,
|
|
9
|
+
* "Let it" / "No", never auto-approved) posted by the agent's own bot in
|
|
10
|
+
* the thread of the work item it is on;
|
|
11
|
+
* 3. applies the answer, whichever place it came from — the card's button,
|
|
12
|
+
* a reaction, a thread reply (批准 / 可以 / yes, 不行 / no), the decisions
|
|
13
|
+
* dashboard, or the Browser page of the dashboard / portal — to the one
|
|
14
|
+
* held action, and tells the agent;
|
|
15
|
+
* 4. at the deadline ({@link BROWSER_APPROVAL_CONSTANTS.DEADLINE_MS}) the
|
|
16
|
+
* answer is No;
|
|
17
|
+
* 5. after a restart, re-attaches a hold whose tab comes back in the
|
|
18
|
+
* extension's tab inventory, and expires the rest (card updated, agent
|
|
19
|
+
* told to redo the step).
|
|
20
|
+
*
|
|
21
|
+
* Approval works exactly like the dashboard's approve always has: it grants
|
|
22
|
+
* the agent ONE pass for its next attempt and tells it to go ahead. Crewly
|
|
23
|
+
* does not replay the click itself — the agent knows what it was in the
|
|
24
|
+
* middle of, and after a wait the page may have changed.
|
|
25
|
+
*
|
|
26
|
+
* Every outcome is applied in one place, {@link BrowserApprovalService.onSettled},
|
|
27
|
+
* which the decision service calls when a `browser_action` card settles.
|
|
28
|
+
* Paths without a card (no Slack, card not created yet) apply it directly.
|
|
29
|
+
*
|
|
30
|
+
* @module services/browser/browser-approval.service
|
|
31
|
+
*/
|
|
32
|
+
import type { OwnerDecision } from '../../types/decision.types.js';
|
|
33
|
+
import type { DecisionKindHandler, PrebuiltAsk } from '../decisions/decision.service.js';
|
|
34
|
+
import { type ComponentLogger } from '../core/logger.service.js';
|
|
35
|
+
import type { BrowserHoldListener, BrowserSession, BrowserSessionService } from './browser-session.service.js';
|
|
36
|
+
import type { ExtensionTabDescriptor } from './browser-bridge.service.js';
|
|
37
|
+
import { HeldActionStore, type HeldBrowserAction } from './held-action-store.js';
|
|
38
|
+
/** The slice of the decision service used here. */
|
|
39
|
+
export interface BrowserApprovalDecisions {
|
|
40
|
+
askPrebuilt(ask: PrebuiltAsk): Promise<OwnerDecision>;
|
|
41
|
+
chooseFromDashboard(id: string, optionKey: string): Promise<OwnerDecision>;
|
|
42
|
+
expire(id: string): Promise<OwnerDecision | null>;
|
|
43
|
+
cancelWhere(filter: (d: OwnerDecision) => boolean, note?: string): Promise<number>;
|
|
44
|
+
}
|
|
45
|
+
/** Collaborators. */
|
|
46
|
+
export interface BrowserApprovalDeps {
|
|
47
|
+
store: HeldActionStore;
|
|
48
|
+
sessions: Pick<BrowserSessionService, 'getSession' | 'resolvePending' | 'restorePending'>;
|
|
49
|
+
/** The decision service, once it runs */
|
|
50
|
+
decisions: () => BrowserApprovalDecisions | null;
|
|
51
|
+
/** Whether a card can be posted now (decision service up, Slack connected) */
|
|
52
|
+
canAskInSlack: () => boolean;
|
|
53
|
+
/** Deliver a line to an agent (paths without a card) */
|
|
54
|
+
tellAgent: (agentSession: string, text: string) => Promise<unknown>;
|
|
55
|
+
/** Display name of an agent ("Vera") */
|
|
56
|
+
agentNameOf?: (agentSession: string) => Promise<string | undefined>;
|
|
57
|
+
/** The tab an agent is bound to */
|
|
58
|
+
boundTabOf?: (agentSession: string) => {
|
|
59
|
+
tabId: number;
|
|
60
|
+
instanceId?: string;
|
|
61
|
+
} | undefined;
|
|
62
|
+
/** Re-bind an agent to an existing tab (after a restart) */
|
|
63
|
+
adoptTab?: (agentSession: string, tabId: number, instanceId?: string) => boolean;
|
|
64
|
+
now?: () => number;
|
|
65
|
+
logger?: ComponentLogger;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Strip a URL to "host/path" for the card.
|
|
69
|
+
*
|
|
70
|
+
* @param url - URL the agent targeted
|
|
71
|
+
* @returns "visa.careerengine.us/subscribe", or undefined
|
|
72
|
+
*/
|
|
73
|
+
export declare function placeOf(url: string | undefined): string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* The visible name of what a click targets, from its params: the text the
|
|
76
|
+
* agent passed, else a quoted name in the selector (`:has-text("Submit")`,
|
|
77
|
+
* `[aria-label="Send"]`, `text=Pay`), else the selector itself.
|
|
78
|
+
*
|
|
79
|
+
* @param params - Params of the call
|
|
80
|
+
* @returns Button text / selector, or undefined
|
|
81
|
+
*/
|
|
82
|
+
export declare function controlName(params: Record<string, unknown> | undefined): string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* What the agent wants to do, in a few words: `click "Submit"`, `press Enter`.
|
|
85
|
+
*
|
|
86
|
+
* @param tool - Tool it called
|
|
87
|
+
* @param params - Params it called it with
|
|
88
|
+
* @returns Short phrase (≤ 100 characters)
|
|
89
|
+
*/
|
|
90
|
+
export declare function describeTarget(tool: string, params: Record<string, unknown> | undefined): string;
|
|
91
|
+
/**
|
|
92
|
+
* The card's question: "Vera wants to click "Submit" on visa.careerengine.us/subscribe — it looks like submitting and can't be undone."
|
|
93
|
+
*
|
|
94
|
+
* @param agentName - Agent display name
|
|
95
|
+
* @param action - Held action
|
|
96
|
+
* @returns One line (≤ 280 characters)
|
|
97
|
+
*/
|
|
98
|
+
export declare function approvalQuestion(agentName: string, action: Pick<HeldBrowserAction, 'target' | 'where' | 'matched'>): string;
|
|
99
|
+
/** The line a held agent is given about where the owner answers. */
|
|
100
|
+
export declare const CARD_ASKED_LINE: string;
|
|
101
|
+
/**
|
|
102
|
+
* Asks the owner about held browser actions and applies the answer.
|
|
103
|
+
*/
|
|
104
|
+
export declare class BrowserApprovalService implements BrowserHoldListener, DecisionKindHandler {
|
|
105
|
+
private static instance;
|
|
106
|
+
private readonly deps;
|
|
107
|
+
private readonly logger;
|
|
108
|
+
private readonly now;
|
|
109
|
+
/** Holds restored from disk, waiting for their tab to come back: pendingId → since (ms) */
|
|
110
|
+
private readonly awaitingRebind;
|
|
111
|
+
/** In-flight async work started from sync hooks (tests wait on it) */
|
|
112
|
+
private readonly inflight;
|
|
113
|
+
private timer;
|
|
114
|
+
private ticking;
|
|
115
|
+
private restored;
|
|
116
|
+
/**
|
|
117
|
+
* @param deps - Collaborators
|
|
118
|
+
*/
|
|
119
|
+
constructor(deps: BrowserApprovalDeps);
|
|
120
|
+
/** @returns The process-wide instance, or null before wiring */
|
|
121
|
+
static getInstance(): BrowserApprovalService | null;
|
|
122
|
+
/** @param service - Instance to install (null clears) */
|
|
123
|
+
static setInstance(service: BrowserApprovalService | null): void;
|
|
124
|
+
/** Start the retry / expiry tick. */
|
|
125
|
+
start(): void;
|
|
126
|
+
/** Stop the tick. */
|
|
127
|
+
stop(): void;
|
|
128
|
+
/** Wait for work started by the sync hooks (tests). */
|
|
129
|
+
idle(): Promise<void>;
|
|
130
|
+
private track;
|
|
131
|
+
/**
|
|
132
|
+
* An action was held: persist it and ask the owner with a card.
|
|
133
|
+
*
|
|
134
|
+
* @param session - Session holding it (`pending` set)
|
|
135
|
+
* @param params - Params of the held call (not persisted)
|
|
136
|
+
* @returns What the agent is told about where the owner answers
|
|
137
|
+
*/
|
|
138
|
+
onHeld(session: BrowserSession, params: Record<string, unknown> | undefined): string | undefined;
|
|
139
|
+
/**
|
|
140
|
+
* Create the card for a held action.
|
|
141
|
+
*
|
|
142
|
+
* @param action - The held action
|
|
143
|
+
*/
|
|
144
|
+
private createCard;
|
|
145
|
+
/**
|
|
146
|
+
* A hold was dropped without an answer: withdraw its card.
|
|
147
|
+
*
|
|
148
|
+
* @param _agentSession - Agent
|
|
149
|
+
* @param pendingId - The dropped hold
|
|
150
|
+
*/
|
|
151
|
+
onDropped(_agentSession: string, pendingId: string): void;
|
|
152
|
+
/**
|
|
153
|
+
* A `browser_action` decision settled (button, reaction, reply, decisions
|
|
154
|
+
* dashboard, Browser page via {@link answerFromBrowserPage}, deadline,
|
|
155
|
+
* expiry or withdrawal). Applies it to the held action.
|
|
156
|
+
*
|
|
157
|
+
* @param decision - The settled decision
|
|
158
|
+
* @returns Note for the agent, or null
|
|
159
|
+
*/
|
|
160
|
+
onSettled(decision: OwnerDecision): Promise<string | null>;
|
|
161
|
+
/**
|
|
162
|
+
* The owner answered on the Browser page (dashboard or portal). With a
|
|
163
|
+
* card, the answer goes through the card so it is updated too; without
|
|
164
|
+
* one it is applied directly.
|
|
165
|
+
*
|
|
166
|
+
* @param agentSession - Session holding the action
|
|
167
|
+
* @param pendingId - The hold being answered
|
|
168
|
+
* @param answer - What the owner chose
|
|
169
|
+
* @returns The updated session, or undefined when no such hold is waiting
|
|
170
|
+
*/
|
|
171
|
+
answerFromBrowserPage(agentSession: string, pendingId: string, answer: 'approve' | 'reject'): Promise<BrowserSession | undefined>;
|
|
172
|
+
/**
|
|
173
|
+
* Apply an outcome to a held action (once) and say what the agent is told.
|
|
174
|
+
*/
|
|
175
|
+
private apply;
|
|
176
|
+
/**
|
|
177
|
+
* After a restart: wait for the tab of each pending hold to come back
|
|
178
|
+
* (tab inventory); holds without a known tab expire right away.
|
|
179
|
+
*
|
|
180
|
+
* @returns How many holds are waiting for their tab
|
|
181
|
+
*/
|
|
182
|
+
restore(): Promise<number>;
|
|
183
|
+
/**
|
|
184
|
+
* A tab inventory arrived: re-bind each restored hold whose tab is in it.
|
|
185
|
+
*
|
|
186
|
+
* @param tabs - Tabs the extension reports
|
|
187
|
+
* @param instanceId - Browser instance that sent it (undefined = direct WS)
|
|
188
|
+
*/
|
|
189
|
+
onTabInventory(tabs: ExtensionTabDescriptor[], instanceId?: string): void;
|
|
190
|
+
/** Expire a hold: through its card when it has one (the card says so), else directly. */
|
|
191
|
+
private expire;
|
|
192
|
+
/**
|
|
193
|
+
* One pass: expire restored holds whose tab never came back, create cards
|
|
194
|
+
* that could not be created, time out holds without a card, prune.
|
|
195
|
+
*
|
|
196
|
+
* @returns Ids acted on
|
|
197
|
+
*/
|
|
198
|
+
tick(): Promise<string[]>;
|
|
199
|
+
}
|
|
200
|
+
/** @returns The process-wide approval service, or null before wiring */
|
|
201
|
+
export declare function getBrowserApprovals(): BrowserApprovalService | null;
|
|
202
|
+
//# sourceMappingURL=browser-approval.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-approval.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/browser/browser-approval.service.ts"],"names":[],"mappings":"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"}
|