crewly 1.20.307 → 1.20.308
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/hooks/agent-status/report.sh +5 -5
- package/dist/backend/backend/src/constants.d.ts +66 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +66 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +9 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +126 -2
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/board/board-activity.normalizer.d.ts +17 -0
- package/dist/backend/backend/src/services/board/board-activity.normalizer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/board-activity.normalizer.js +8 -5
- package/dist/backend/backend/src/services/board/board-activity.normalizer.js.map +1 -1
- package/dist/backend/backend/src/services/board/board-activity.tracker.d.ts +5 -0
- package/dist/backend/backend/src/services/board/board-activity.tracker.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/board-activity.tracker.js +25 -1
- package/dist/backend/backend/src/services/board/board-activity.tracker.js.map +1 -1
- package/dist/backend/backend/src/services/board/board-push.service.d.ts +20 -2
- package/dist/backend/backend/src/services/board/board-push.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/board-push.service.js +71 -9
- package/dist/backend/backend/src/services/board/board-push.service.js.map +1 -1
- package/dist/backend/backend/src/services/board/board-snapshot.builder.d.ts +7 -0
- package/dist/backend/backend/src/services/board/board-snapshot.builder.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/board-snapshot.builder.js +2 -0
- package/dist/backend/backend/src/services/board/board-snapshot.builder.js.map +1 -1
- package/dist/backend/backend/src/services/board/board.sources.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/board.sources.js +7 -0
- package/dist/backend/backend/src/services/board/board.sources.js.map +1 -1
- package/dist/backend/backend/src/services/board/board.types.d.ts +5 -0
- package/dist/backend/backend/src/services/board/board.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/board/canvas/canvas-inbound.service.d.ts +122 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-inbound.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-inbound.service.js +339 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-inbound.service.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-redact.d.ts +44 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-redact.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-redact.js +161 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-redact.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.normalizer.d.ts +57 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.normalizer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.normalizer.js +164 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.normalizer.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.stream.d.ts +140 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.stream.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.stream.js +300 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-step.stream.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-terminal.service.d.ts +99 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-terminal.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-terminal.service.js +223 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-terminal.service.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-transcript.tail.d.ts +126 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-transcript.tail.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-transcript.tail.js +385 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-transcript.tail.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-wiring.d.ts +103 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-wiring.js +124 -0
- package/dist/backend/backend/src/services/board/canvas/canvas-wiring.js.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas.types.d.ts +88 -0
- package/dist/backend/backend/src/services/board/canvas/canvas.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/board/canvas/canvas.types.js +8 -0
- package/dist/backend/backend/src/services/board/canvas/canvas.types.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +66 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +66 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CanvasInboundService - the machine side of the owner actions on the Crewly
|
|
3
|
+
* Canvas (crewly#1247 v2).
|
|
4
|
+
*
|
|
5
|
+
* The owner uses the canvas in the Crewly Cloud portal: message an agent, stop
|
|
6
|
+
* an agent, open / renew / close an agent's terminal. Cloud stores the action
|
|
7
|
+
* and pushes a `canvas_action` relay message with only ids in it
|
|
8
|
+
* (`{actionId, instanceId, session, kind}`) - the same secure pattern as Cloud
|
|
9
|
+
* Talk and "waiting on you". This service:
|
|
10
|
+
*
|
|
11
|
+
* 1. checks the push is for this machine and names an agent that exists here;
|
|
12
|
+
* 2. fetches the action from Cloud with this machine's own token
|
|
13
|
+
* (`GET /api/cloud/canvas/actions/:actionId?instanceId=`). Cloud answers
|
|
14
|
+
* only for an action stored by the OWNER of the token's account for this
|
|
15
|
+
* machine, so a push anybody else dropped in the relay queue (or one from a
|
|
16
|
+
* member / guest of the account) is never run; the fetched ids and kind
|
|
17
|
+
* must match the push;
|
|
18
|
+
* 3. runs it: a message is recorded in the agent's DM as an owner message and
|
|
19
|
+
* handed to the agent exactly like a Talk / Crewly Chat message (replies
|
|
20
|
+
* route as usual); a stop is the dashboard's stop; terminal actions drive
|
|
21
|
+
* {@link CanvasTerminalService};
|
|
22
|
+
* 4. reports the result (`POST .../actions/:actionId/result`).
|
|
23
|
+
*
|
|
24
|
+
* Idempotent: an action carried out is not run again when Cloud re-pushes it
|
|
25
|
+
* (it only re-reports); a message is also keyed by `clientMessageId` in the DM.
|
|
26
|
+
*
|
|
27
|
+
* Older OSS versions ignore `canvas_action` (every relay listener filters on
|
|
28
|
+
* its own `type`), and Cloud only sends it to machines advertising the
|
|
29
|
+
* `canvas_actions` capability, which this service turns on when it starts.
|
|
30
|
+
*
|
|
31
|
+
* @module services/board/canvas/canvas-inbound.service
|
|
32
|
+
*/
|
|
33
|
+
import { CANVAS_CONSTANTS, ORCHESTRATOR_SESSION_NAME, SLACK_AGENT_DM_CONSTANTS } from '../../../constants.js';
|
|
34
|
+
import { LoggerService } from '../../core/logger.service.js';
|
|
35
|
+
import { recordCloudTalkTurn } from '../../chat-v2/owner-inbound.utils.js';
|
|
36
|
+
const C = CANVAS_CONSTANTS;
|
|
37
|
+
/** Session names; `@` only for the orchestrator spelled per machine (`crewly-orc@<instance>`). */
|
|
38
|
+
const SESSION_PATTERN = /^[A-Za-z0-9._@-]{1,128}$/;
|
|
39
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
40
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
41
|
+
const isKind = (v) => typeof v === 'string' && C.ACTION_KINDS.includes(v);
|
|
42
|
+
/**
|
|
43
|
+
* Validate the relay push. Never throws.
|
|
44
|
+
*
|
|
45
|
+
* @param raw - `msg.payload`
|
|
46
|
+
* @returns The ids, or null when malformed
|
|
47
|
+
*/
|
|
48
|
+
export function parseCanvasRelayData(raw) {
|
|
49
|
+
if (!isRecord(raw))
|
|
50
|
+
return null;
|
|
51
|
+
const { actionId, instanceId, session, kind } = raw;
|
|
52
|
+
if (typeof actionId !== 'string' || !ID_PATTERN.test(actionId))
|
|
53
|
+
return null;
|
|
54
|
+
if (typeof instanceId !== 'string' || instanceId.length === 0 || instanceId.length > 128)
|
|
55
|
+
return null;
|
|
56
|
+
if (typeof session !== 'string' || !SESSION_PATTERN.test(session))
|
|
57
|
+
return null;
|
|
58
|
+
if (!isKind(kind))
|
|
59
|
+
return null;
|
|
60
|
+
return { actionId, instanceId, session, kind };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate Cloud's answer to the action fetch (`{success, data: {...}}`). Never throws.
|
|
64
|
+
*
|
|
65
|
+
* @param raw - Parsed JSON body
|
|
66
|
+
* @returns The action, or null when unusable
|
|
67
|
+
*/
|
|
68
|
+
export function parseCanvasFetchResponse(raw) {
|
|
69
|
+
const data = isRecord(raw) && isRecord(raw['data']) ? raw['data'] : null;
|
|
70
|
+
if (!data)
|
|
71
|
+
return null;
|
|
72
|
+
const { actionId, instanceId, session, kind, text, clientMessageId } = data;
|
|
73
|
+
if (typeof actionId !== 'string' || !ID_PATTERN.test(actionId))
|
|
74
|
+
return null;
|
|
75
|
+
if (typeof instanceId !== 'string' || !instanceId)
|
|
76
|
+
return null;
|
|
77
|
+
if (typeof session !== 'string' || !SESSION_PATTERN.test(session))
|
|
78
|
+
return null;
|
|
79
|
+
if (!isKind(kind))
|
|
80
|
+
return null;
|
|
81
|
+
return {
|
|
82
|
+
actionId,
|
|
83
|
+
instanceId,
|
|
84
|
+
session,
|
|
85
|
+
kind,
|
|
86
|
+
...(typeof text === 'string' ? { text } : {}),
|
|
87
|
+
...(typeof clientMessageId === 'string' && ID_PATTERN.test(clientMessageId) ? { clientMessageId } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
let active = false;
|
|
91
|
+
/**
|
|
92
|
+
* Capabilities to advertise to Cloud (heartbeat and uploads).
|
|
93
|
+
*
|
|
94
|
+
* @returns `['canvas_actions']` while the handler runs, else `[]`
|
|
95
|
+
*/
|
|
96
|
+
export function canvasCapabilities() {
|
|
97
|
+
return active ? [C.CAPABILITY] : [];
|
|
98
|
+
}
|
|
99
|
+
/** Runs the owner's canvas actions pushed from Crewly Cloud - see module docs. */
|
|
100
|
+
export class CanvasInboundService {
|
|
101
|
+
deps;
|
|
102
|
+
logger;
|
|
103
|
+
fetchImpl;
|
|
104
|
+
sleep;
|
|
105
|
+
now;
|
|
106
|
+
ownerUserId;
|
|
107
|
+
inFlight = new Set();
|
|
108
|
+
done = new Map();
|
|
109
|
+
listener = (msg) => {
|
|
110
|
+
void this.handle(msg);
|
|
111
|
+
};
|
|
112
|
+
started = false;
|
|
113
|
+
constructor(deps) {
|
|
114
|
+
this.deps = deps;
|
|
115
|
+
this.logger = deps.logger ?? LoggerService.getInstance().createComponentLogger('CanvasInbound');
|
|
116
|
+
this.fetchImpl = deps.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
117
|
+
this.sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
118
|
+
this.now = deps.now ?? Date.now;
|
|
119
|
+
this.ownerUserId = deps.ownerUserId ?? SLACK_AGENT_DM_CONSTANTS.OWNER_USER_ID;
|
|
120
|
+
}
|
|
121
|
+
/** Subscribe to relay messages and advertise the capability. Idempotent. */
|
|
122
|
+
start() {
|
|
123
|
+
if (this.started)
|
|
124
|
+
return;
|
|
125
|
+
this.started = true;
|
|
126
|
+
this.deps.source.on('message', this.listener);
|
|
127
|
+
active = true;
|
|
128
|
+
this.logger.info('Canvas action handler started');
|
|
129
|
+
}
|
|
130
|
+
/** Unsubscribe; the capability is no longer advertised. */
|
|
131
|
+
stop() {
|
|
132
|
+
if (!this.started)
|
|
133
|
+
return;
|
|
134
|
+
this.started = false;
|
|
135
|
+
this.deps.source.off('message', this.listener);
|
|
136
|
+
active = false;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle one relay message. Anything but a well-formed `canvas_action` is
|
|
140
|
+
* ignored. Never throws.
|
|
141
|
+
*
|
|
142
|
+
* @param msg - Relay message from CloudSyncService
|
|
143
|
+
* @returns What was done
|
|
144
|
+
*/
|
|
145
|
+
async handle(msg) {
|
|
146
|
+
if (msg?.type !== C.RELAY_MESSAGE_TYPE)
|
|
147
|
+
return 'ignored';
|
|
148
|
+
const data = parseCanvasRelayData(msg.payload);
|
|
149
|
+
if (!data) {
|
|
150
|
+
this.logger.warn('Ignored a malformed canvas_action relay message');
|
|
151
|
+
return 'ignored';
|
|
152
|
+
}
|
|
153
|
+
if (this.inFlight.has(data.actionId))
|
|
154
|
+
return 'duplicate';
|
|
155
|
+
this.inFlight.add(data.actionId);
|
|
156
|
+
try {
|
|
157
|
+
return await this.process(data);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.logger.warn('Canvas action failed', { actionId: data.actionId, error: error instanceof Error ? error.message : String(error) });
|
|
161
|
+
return 'fetch_failed';
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
this.inFlight.delete(data.actionId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async process(data) {
|
|
168
|
+
const { instanceId, deviceName } = await this.deps.identity();
|
|
169
|
+
if (data.instanceId !== instanceId) {
|
|
170
|
+
this.logger.warn('Ignored a canvas_action addressed to another machine', { target: data.instanceId });
|
|
171
|
+
return 'not_for_this_machine';
|
|
172
|
+
}
|
|
173
|
+
this.pruneDone();
|
|
174
|
+
const earlier = this.done.get(data.actionId);
|
|
175
|
+
if (earlier) {
|
|
176
|
+
await this.report(data.actionId, earlier.result);
|
|
177
|
+
return 'duplicate';
|
|
178
|
+
}
|
|
179
|
+
const fetched = await this.fetchAction(data, instanceId);
|
|
180
|
+
if (fetched.kind === 'not_found') {
|
|
181
|
+
// Not an action the account's owner stored for this machine: not from Cloud
|
|
182
|
+
this.logger.warn('Ignored a canvas_action Crewly Cloud does not know for this machine', { actionId: data.actionId });
|
|
183
|
+
return 'unverified';
|
|
184
|
+
}
|
|
185
|
+
if (fetched.kind === 'expired')
|
|
186
|
+
return 'duplicate';
|
|
187
|
+
if (fetched.kind === 'error') {
|
|
188
|
+
this.logger.warn('Could not fetch a canvas action; Cloud will push it again', { actionId: data.actionId, error: fetched.error });
|
|
189
|
+
return 'fetch_failed';
|
|
190
|
+
}
|
|
191
|
+
const action = fetched.action;
|
|
192
|
+
if (action.actionId !== data.actionId || action.instanceId !== instanceId || action.session !== data.session || action.kind !== data.kind) {
|
|
193
|
+
this.logger.warn('Ignored a canvas_action that does not match what Cloud stored', { actionId: data.actionId });
|
|
194
|
+
return 'unverified';
|
|
195
|
+
}
|
|
196
|
+
this.deps.onAction?.();
|
|
197
|
+
const device = deviceName || 'this machine';
|
|
198
|
+
const session = action.session.startsWith(`${ORCHESTRATOR_SESSION_NAME}@`) ? ORCHESTRATOR_SESSION_NAME : action.session;
|
|
199
|
+
let result;
|
|
200
|
+
if (!(await this.deps.agentExists(session))) {
|
|
201
|
+
result = { instanceId, ok: false, code: 'no_agent', error: `No agent named "${session}" on ${device}` };
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
result = await this.run(action, session, instanceId, device);
|
|
205
|
+
}
|
|
206
|
+
this.done.set(data.actionId, { at: this.now(), result });
|
|
207
|
+
await this.report(data.actionId, result);
|
|
208
|
+
return result.ok ? 'done' : 'refused';
|
|
209
|
+
}
|
|
210
|
+
async run(action, session, instanceId, device) {
|
|
211
|
+
const fail = (code, error) => ({ instanceId, ok: false, code, error });
|
|
212
|
+
switch (action.kind) {
|
|
213
|
+
case 'message': {
|
|
214
|
+
const text = (action.text ?? '').trim();
|
|
215
|
+
if (!text)
|
|
216
|
+
return fail('empty', 'The message is empty');
|
|
217
|
+
if (text.length > C.MESSAGE_MAX)
|
|
218
|
+
return fail('too_long', `The message is longer than ${C.MESSAGE_MAX} characters`);
|
|
219
|
+
let recorded;
|
|
220
|
+
try {
|
|
221
|
+
recorded = recordCloudTalkTurn(this.deps.chat, {
|
|
222
|
+
agentSession: session,
|
|
223
|
+
text,
|
|
224
|
+
clientMessageId: action.clientMessageId ?? `canvas-${action.actionId}`,
|
|
225
|
+
ownerUserId: this.ownerUserId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return fail('record_failed', `Could not record the message on ${device}: ${error instanceof Error ? error.message : String(error)}`);
|
|
230
|
+
}
|
|
231
|
+
if (recorded.deduped)
|
|
232
|
+
return { instanceId, ok: true };
|
|
233
|
+
try {
|
|
234
|
+
const channel = this.deps.chat.getChannel(recorded.channelId, { userId: this.ownerUserId, source: 'oss' });
|
|
235
|
+
await this.deps.deliver(channel, recorded.message);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
// Recorded, but the agent was not paged: it reads the DM when it next looks
|
|
239
|
+
this.logger.warn('Canvas message recorded but not handed to the agent', { session, error: error instanceof Error ? error.message : String(error) });
|
|
240
|
+
}
|
|
241
|
+
this.logger.info('Canvas message received', { session });
|
|
242
|
+
return { instanceId, ok: true };
|
|
243
|
+
}
|
|
244
|
+
case 'stop': {
|
|
245
|
+
if (session === ORCHESTRATOR_SESSION_NAME)
|
|
246
|
+
return fail('not_allowed', 'The orchestrator is stopped on the machine itself');
|
|
247
|
+
const r = await this.deps.stopAgent(session);
|
|
248
|
+
this.logger.info('Canvas stop', { session, ok: r.ok });
|
|
249
|
+
return r.ok ? { instanceId, ok: true } : fail('stop_failed', r.error ?? 'Could not stop the agent');
|
|
250
|
+
}
|
|
251
|
+
case 'terminal_open':
|
|
252
|
+
case 'terminal_renew': {
|
|
253
|
+
if (action.kind === 'terminal_renew' && this.deps.terminal.renew(session))
|
|
254
|
+
return { instanceId, ok: true };
|
|
255
|
+
// A renew after a restart opens again: the viewer is still there
|
|
256
|
+
const r = this.deps.terminal.open(session);
|
|
257
|
+
return r.ok ? { instanceId, ok: true } : fail(r.code, r.code === 'too_many_streams' ? 'Too many terminals are open' : r.code === 'no_session' ? 'The agent has no terminal right now' : 'Not signed in to Crewly Cloud');
|
|
258
|
+
}
|
|
259
|
+
case 'terminal_close':
|
|
260
|
+
this.deps.terminal.close(session, 'closed');
|
|
261
|
+
return { instanceId, ok: true };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
pruneDone() {
|
|
265
|
+
const cutoff = this.now() - C.DONE_ACTIONS_TTL_MS;
|
|
266
|
+
for (const [id, entry] of this.done)
|
|
267
|
+
if (entry.at < cutoff)
|
|
268
|
+
this.done.delete(id);
|
|
269
|
+
while (this.done.size > C.DONE_ACTIONS_MAX)
|
|
270
|
+
this.done.delete(this.done.keys().next().value);
|
|
271
|
+
}
|
|
272
|
+
url(suffix) {
|
|
273
|
+
const base = this.deps.cloud.getCloudUrl();
|
|
274
|
+
return base ? `${base.replace(/\/$/, '')}${C.ACTIONS_PATH}${suffix}` : null;
|
|
275
|
+
}
|
|
276
|
+
/** Fetch the action from Cloud with this machine's token; one refresh on 401, a few retries on network / 5xx. */
|
|
277
|
+
async fetchAction(data, instanceId) {
|
|
278
|
+
const url = this.url(`/${encodeURIComponent(data.actionId)}?instanceId=${encodeURIComponent(instanceId)}`);
|
|
279
|
+
if (!url)
|
|
280
|
+
return { kind: 'error', error: 'not signed in to Crewly Cloud' };
|
|
281
|
+
const delays = C.FETCH_RETRY_DELAYS_MS;
|
|
282
|
+
let refreshed = false;
|
|
283
|
+
let lastError = 'unknown';
|
|
284
|
+
for (let attempt = 0; attempt <= delays.length; attempt++) {
|
|
285
|
+
if (attempt > 0)
|
|
286
|
+
await this.sleep(delays[attempt - 1] ?? 0);
|
|
287
|
+
const token = this.deps.cloud.getToken();
|
|
288
|
+
if (!token)
|
|
289
|
+
return { kind: 'error', error: 'not signed in to Crewly Cloud' };
|
|
290
|
+
let response;
|
|
291
|
+
try {
|
|
292
|
+
response = await this.fetchImpl(url, { method: 'GET', headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(C.REQUEST_TIMEOUT_MS) });
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (response.status === 401 && !refreshed && this.deps.cloud.tryRefreshToken) {
|
|
299
|
+
refreshed = true;
|
|
300
|
+
if (await this.deps.cloud.tryRefreshToken().catch(() => false)) {
|
|
301
|
+
attempt--;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// 403: the token's account is not the owner of this action (a member / guest)
|
|
306
|
+
if (response.status === 404 || response.status === 403)
|
|
307
|
+
return { kind: 'not_found' };
|
|
308
|
+
if (response.status === 410)
|
|
309
|
+
return { kind: 'expired' };
|
|
310
|
+
if (response.ok) {
|
|
311
|
+
const parsed = parseCanvasFetchResponse(await response.json().catch(() => null));
|
|
312
|
+
return parsed ? { kind: 'ok', action: parsed } : { kind: 'error', error: 'unusable response from Crewly Cloud' };
|
|
313
|
+
}
|
|
314
|
+
lastError = `HTTP ${response.status}`;
|
|
315
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429)
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
return { kind: 'error', error: lastError };
|
|
319
|
+
}
|
|
320
|
+
/** Report the result to Cloud (best effort; a re-push re-reports). */
|
|
321
|
+
async report(actionId, body) {
|
|
322
|
+
const url = this.url(`/${encodeURIComponent(actionId)}/result`);
|
|
323
|
+
const token = this.deps.cloud.getToken();
|
|
324
|
+
if (!url || !token)
|
|
325
|
+
return;
|
|
326
|
+
try {
|
|
327
|
+
await this.fetchImpl(url, {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
330
|
+
body: JSON.stringify(body),
|
|
331
|
+
signal: AbortSignal.timeout(C.REQUEST_TIMEOUT_MS),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
this.logger.debug('Could not report a canvas action result', { actionId, error: error instanceof Error ? error.message : String(error) });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=canvas-inbound.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-inbound.service.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/board/canvas/canvas-inbound.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for what the canvas shows (crewly#1247 v2), on the machine, before
|
|
3
|
+
* anything leaves it:
|
|
4
|
+
*
|
|
5
|
+
* - {@link shortAgentLine}: the agent's own words from a transcript, reduced
|
|
6
|
+
* to one short line (at most 280 characters), no code blocks, no secrets;
|
|
7
|
+
* - {@link maskTerminalText}: a terminal tail with every secret, token, email
|
|
8
|
+
* and URL query masked.
|
|
9
|
+
*
|
|
10
|
+
* Both reuse the board's {@link scrubText} and the secret redactor that guards
|
|
11
|
+
* the session logs; this file only adds what those do not cover (zero-width
|
|
12
|
+
* characters, base64 blobs, spaced `key = value` secrets, ANSI).
|
|
13
|
+
*
|
|
14
|
+
* Pure (no I/O).
|
|
15
|
+
*
|
|
16
|
+
* @module services/board/canvas/canvas-redact
|
|
17
|
+
*/
|
|
18
|
+
import type { SecretEnvValue } from '../../../utils/secret-env.js';
|
|
19
|
+
/**
|
|
20
|
+
* Reduce an assistant message block to one short, safe line.
|
|
21
|
+
*
|
|
22
|
+
* Takes the first paragraph outside code fences (a list item or heading loses
|
|
23
|
+
* its marker), removes markdown emphasis, masks secrets / emails / URL
|
|
24
|
+
* queries / phone numbers, and cuts at 280 characters. Anything that is
|
|
25
|
+
* markup (`<system-reminder>`), an interrupt notice, or empty after masking
|
|
26
|
+
* gives null.
|
|
27
|
+
*
|
|
28
|
+
* @param raw - Text of a `text` block
|
|
29
|
+
* @param secrets - Exact secret values to mask (backend env)
|
|
30
|
+
* @returns The line, or null when nothing worth showing is left
|
|
31
|
+
*/
|
|
32
|
+
export declare function shortAgentLine(raw: unknown, secrets?: readonly SecretEnvValue[]): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Mask a terminal tail for the canvas: ANSI and control characters removed,
|
|
35
|
+
* then secrets (by value, by `NAME=value`, by shape), emails, URL queries and
|
|
36
|
+
* credentials, bearer tokens, password prompts and long token-like words.
|
|
37
|
+
*
|
|
38
|
+
* @param text - Raw tail (the PTY capture)
|
|
39
|
+
* @param secrets - Exact secret values to mask (backend env)
|
|
40
|
+
* @param maxLines - Keep this many lines from the end
|
|
41
|
+
* @returns Lines, oldest first, each at most {@link CANVAS_CONSTANTS.TERMINAL_MAX_LINE_CHARS} characters
|
|
42
|
+
*/
|
|
43
|
+
export declare function maskTerminalText(text: string, secrets?: readonly SecretEnvValue[], maxLines?: number): string[];
|
|
44
|
+
//# sourceMappingURL=canvas-redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-redact.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/board/canvas/canvas-redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAgEnE;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,SAAS,cAAc,EAAO,GAAG,MAAM,GAAG,IAAI,CAsBnG;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,cAAc,EAAO,EAAE,QAAQ,GAAE,MAA8B,GAAG,MAAM,EAAE,CA6B1I"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for what the canvas shows (crewly#1247 v2), on the machine, before
|
|
3
|
+
* anything leaves it:
|
|
4
|
+
*
|
|
5
|
+
* - {@link shortAgentLine}: the agent's own words from a transcript, reduced
|
|
6
|
+
* to one short line (at most 280 characters), no code blocks, no secrets;
|
|
7
|
+
* - {@link maskTerminalText}: a terminal tail with every secret, token, email
|
|
8
|
+
* and URL query masked.
|
|
9
|
+
*
|
|
10
|
+
* Both reuse the board's {@link scrubText} and the secret redactor that guards
|
|
11
|
+
* the session logs; this file only adds what those do not cover (zero-width
|
|
12
|
+
* characters, base64 blobs, spaced `key = value` secrets, ANSI).
|
|
13
|
+
*
|
|
14
|
+
* Pure (no I/O).
|
|
15
|
+
*
|
|
16
|
+
* @module services/board/canvas/canvas-redact
|
|
17
|
+
*/
|
|
18
|
+
import { CANVAS_CONSTANTS } from '../../../constants.js';
|
|
19
|
+
import { redactSecrets } from '../../../utils/secret-redactor.js';
|
|
20
|
+
import { clip, looksLikeToken, REDACTED, scrubText } from '../board-redact.js';
|
|
21
|
+
const C = CANVAS_CONSTANTS;
|
|
22
|
+
/** A terminal capture is cut to this many characters from the end before any pattern runs. */
|
|
23
|
+
const MAX_RAW_CHARS = 400_000;
|
|
24
|
+
/** A raw line is cut to this many characters (an escape-heavy line shrinks after ANSI removal). */
|
|
25
|
+
const MAX_RAW_LINE = 6_000;
|
|
26
|
+
/** A line is masked over its first this many characters (the displayed 400 plus room for a secret that straddles the cut; the cost is quadratic in it). */
|
|
27
|
+
const MASK_LINE_CHARS = 600;
|
|
28
|
+
// eslint-disable-next-line no-control-regex
|
|
29
|
+
const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[PX^_][^\x1b]*\x1b\\|\x1b[@-Z\\^_=>]|\x9b[0-9;?]*[ -/]*[@-~]/g;
|
|
30
|
+
/** Characters that hide text or reorder it. */
|
|
31
|
+
const INVISIBLE = /[\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/g;
|
|
32
|
+
/** A long run of base64 / base64url, or a data: URI payload. */
|
|
33
|
+
const BASE64_BLOB = /data:[\w/+.-]+;base64,[A-Za-z0-9+/_=-]+|[A-Za-z0-9+_]{40,}={0,2}/g;
|
|
34
|
+
/** `secret_key = value` with spaces around the separator (the shared redactor wants `NAME=value`). */
|
|
35
|
+
const SPACED_SECRET = /((?:api[_ -]?key|secret|token|passw(?:or)?d|passphrase|private[_ -]?key|credential|auth)[\w-]*\s*(?:[:=]|is)\s+)["']?[^\s"',;]{4,}/gi;
|
|
36
|
+
const PROMPT_SECRET = /((?:password|passphrase|pin|otp|verification code|2fa code)(?:\s+for\s+\S+)?\s*(?:\([^)]*\))?\s*[:?]\s*)\S.*$/gim;
|
|
37
|
+
/** `[REDACTED NAME]` from the shared redactor, without the name (which env var it was is not Cloud's business). */
|
|
38
|
+
const REDACTED_MASK = /\[REDACTED[^\]]*\]/g;
|
|
39
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/g;
|
|
40
|
+
const URL_QUERY = /(\bhttps?:\/\/[^\s?#"'<>]+)\?[^\s"'<>]*/gi;
|
|
41
|
+
const URL_CREDENTIALS = /(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi;
|
|
42
|
+
const BEARER = /\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]{6,}/gi;
|
|
43
|
+
const HEX_LONG = /\b[0-9a-fA-F]{32,}\b/g;
|
|
44
|
+
/** A phone number: international (`+...`) or three-three-four groups. Dates, versions and model ids are not. */
|
|
45
|
+
const PHONE = /(?<![\w.-])(?:\+\d[\d\s().-]{8,}\d|\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4})(?![\w-])/g;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a word of a terminal line looks like a credential. Slugs such as
|
|
48
|
+
* `claude-opus-4-5-20251101` (lowercase words joined by dashes) are not.
|
|
49
|
+
*
|
|
50
|
+
* @param word - Word without surrounding punctuation
|
|
51
|
+
* @returns True when it should be masked
|
|
52
|
+
*/
|
|
53
|
+
function looksSecretWord(word) {
|
|
54
|
+
if (word.length < 24 || !/^[A-Za-z0-9_+=/-]+$/.test(word))
|
|
55
|
+
return false;
|
|
56
|
+
if (!/\d/.test(word) || !/[A-Za-z]/.test(word))
|
|
57
|
+
return false;
|
|
58
|
+
if (/^[a-z0-9]+(?:-[a-z0-9.]+)+$/.test(word))
|
|
59
|
+
return false;
|
|
60
|
+
if (word.includes('/'))
|
|
61
|
+
return false; // a path
|
|
62
|
+
return looksLikeToken(word) || (/[A-Z]/.test(word) && /[a-z]/.test(word));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Mask what must not be shown in either surface.
|
|
66
|
+
*
|
|
67
|
+
* @param text - One line or a whole text
|
|
68
|
+
* @returns Text with emails, URL queries / credentials, bearer tokens, long hex, blobs and spaced secrets masked
|
|
69
|
+
*/
|
|
70
|
+
function maskCommon(text) {
|
|
71
|
+
let s = text;
|
|
72
|
+
s = s.replace(URL_CREDENTIALS, `$1${REDACTED}@`);
|
|
73
|
+
s = s.replace(URL_QUERY, '$1?[query]');
|
|
74
|
+
s = s.replace(EMAIL, '[email]');
|
|
75
|
+
s = s.replace(BEARER, REDACTED);
|
|
76
|
+
s = s.replace(SPACED_SECRET, `$1${REDACTED}`);
|
|
77
|
+
s = s.replace(HEX_LONG, REDACTED);
|
|
78
|
+
s = s.replace(BASE64_BLOB, (m) => (m.startsWith('data:') || m.endsWith('=') || (/\d/.test(m) && /[A-Z]/.test(m) && /[a-z]/.test(m)) ? REDACTED : m));
|
|
79
|
+
return s;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reduce an assistant message block to one short, safe line.
|
|
83
|
+
*
|
|
84
|
+
* Takes the first paragraph outside code fences (a list item or heading loses
|
|
85
|
+
* its marker), removes markdown emphasis, masks secrets / emails / URL
|
|
86
|
+
* queries / phone numbers, and cuts at 280 characters. Anything that is
|
|
87
|
+
* markup (`<system-reminder>`), an interrupt notice, or empty after masking
|
|
88
|
+
* gives null.
|
|
89
|
+
*
|
|
90
|
+
* @param raw - Text of a `text` block
|
|
91
|
+
* @param secrets - Exact secret values to mask (backend env)
|
|
92
|
+
* @returns The line, or null when nothing worth showing is left
|
|
93
|
+
*/
|
|
94
|
+
export function shortAgentLine(raw, secrets = []) {
|
|
95
|
+
if (typeof raw !== 'string')
|
|
96
|
+
return null;
|
|
97
|
+
let s = raw.length > 6000 ? raw.slice(0, 6000) : raw;
|
|
98
|
+
s = s.replace(INVISIBLE, '').replace(ANSI, '');
|
|
99
|
+
// Code is never a "short line": drop fenced blocks (an unclosed fence runs to the end)
|
|
100
|
+
s = s.replace(/```[\s\S]*?(?:```|$)/g, '\n\n').replace(/~~~[\s\S]*?(?:~~~|$)/g, '\n\n');
|
|
101
|
+
const paragraphs = s
|
|
102
|
+
.split(/\n\s*\n/)
|
|
103
|
+
.map((p) => p.trim())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
let first = paragraphs.find((p) => !/^(?:\||<|\[Request interrupted|No response requested|API Error)/i.test(p)) ?? '';
|
|
106
|
+
// Soft-wrapped lines are one sentence; a list shows its first item
|
|
107
|
+
const lines = first.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
108
|
+
first = /^(?:[-*+]|\d+[.)])\s/.test(lines[0] ?? '') ? (lines[0] ?? '') : lines.join(' ');
|
|
109
|
+
first = first
|
|
110
|
+
.replace(/^(?:#{1,6}|[-*+]|\d+[.)]|>)\s+/, '')
|
|
111
|
+
.replace(/\[([^\]]{1,120})\]\(([^)\s]*)\)/g, '$1')
|
|
112
|
+
.replace(/(\*\*|__|\*|_|`)/g, '');
|
|
113
|
+
if (first.length < 2 || /^<[a-z/!]/i.test(first))
|
|
114
|
+
return null;
|
|
115
|
+
const out = scrubText(maskCommon(redactSecrets(first, secrets).replace(REDACTED_MASK, REDACTED)).replace(PHONE, '[number]'), C.TEXT_MAX);
|
|
116
|
+
if (!/[\p{L}\p{N}]/u.test(out.replace(/\[(?:redacted|email|number|query)\]/gi, '')))
|
|
117
|
+
return null;
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Mask a terminal tail for the canvas: ANSI and control characters removed,
|
|
122
|
+
* then secrets (by value, by `NAME=value`, by shape), emails, URL queries and
|
|
123
|
+
* credentials, bearer tokens, password prompts and long token-like words.
|
|
124
|
+
*
|
|
125
|
+
* @param text - Raw tail (the PTY capture)
|
|
126
|
+
* @param secrets - Exact secret values to mask (backend env)
|
|
127
|
+
* @param maxLines - Keep this many lines from the end
|
|
128
|
+
* @returns Lines, oldest first, each at most {@link CANVAS_CONSTANTS.TERMINAL_MAX_LINE_CHARS} characters
|
|
129
|
+
*/
|
|
130
|
+
export function maskTerminalText(text, secrets = [], maxLines = C.TERMINAL_TAIL_LINES) {
|
|
131
|
+
// Bound the work first: the patterns below are linear per line, but a single
|
|
132
|
+
// megabyte-long line of letters would still cost seconds in the email pattern
|
|
133
|
+
const bounded = (text.length > MAX_RAW_CHARS ? text.slice(-MAX_RAW_CHARS) : text)
|
|
134
|
+
.split('\n')
|
|
135
|
+
.slice(-(maxLines + 20))
|
|
136
|
+
.map((l) => (l.length > MAX_RAW_LINE ? l.slice(0, MAX_RAW_LINE) : l));
|
|
137
|
+
let s = bounded.join('\n').replace(/\r\n/g, '\n').replace(ANSI, '').replace(INVISIBLE, '');
|
|
138
|
+
// A carriage return redraws the line: keep what was drawn last
|
|
139
|
+
s = s
|
|
140
|
+
.split('\n')
|
|
141
|
+
.map((l) => {
|
|
142
|
+
const drawn = l.includes('\r') ? (l.split('\r').filter(Boolean).pop() ?? '') : l;
|
|
143
|
+
return drawn.length > MASK_LINE_CHARS ? drawn.slice(0, MASK_LINE_CHARS) : drawn;
|
|
144
|
+
})
|
|
145
|
+
.join('\n');
|
|
146
|
+
// eslint-disable-next-line no-control-regex
|
|
147
|
+
s = s.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, '');
|
|
148
|
+
// Whole-text passes first: a private key block spans lines
|
|
149
|
+
s = redactSecrets(s, secrets).replace(REDACTED_MASK, REDACTED);
|
|
150
|
+
s = s.replace(PROMPT_SECRET, `$1${REDACTED}`);
|
|
151
|
+
const lines = s.split('\n').map((line) => {
|
|
152
|
+
let l = maskCommon(line);
|
|
153
|
+
l = l.replace(PHONE, '[number]');
|
|
154
|
+
l = l.replace(/[A-Za-z0-9_+=/-]{24,}/g, (w) => (looksSecretWord(w) ? REDACTED : w));
|
|
155
|
+
return clip(l.replace(/\s+$/, ''), C.TERMINAL_MAX_LINE_CHARS);
|
|
156
|
+
});
|
|
157
|
+
while (lines.length > 0 && lines[lines.length - 1] === '')
|
|
158
|
+
lines.pop();
|
|
159
|
+
return lines.slice(-maxLines);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=canvas-redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-redact.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/board/canvas/canvas-redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,CAAC,GAAG,gBAAgB,CAAC;AAE3B,8FAA8F;AAC9F,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,mGAAmG;AACnG,MAAM,YAAY,GAAG,KAAK,CAAC;AAC3B,2JAA2J;AAC3J,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,4CAA4C;AAC5C,MAAM,IAAI,GAAG,+HAA+H,CAAC;AAC7I,+CAA+C;AAC/C,MAAM,SAAS,GAAG,kDAAkD,CAAC;AACrE,gEAAgE;AAChE,MAAM,WAAW,GAAG,mEAAmE,CAAC;AACxF,sGAAsG;AACtG,MAAM,aAAa,GAAG,sIAAsI,CAAC;AAC7J,MAAM,aAAa,GAAG,kHAAkH,CAAC;AACzI,mHAAmH;AACnH,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,MAAM,KAAK,GAAG,oEAAoE,CAAC;AACnF,MAAM,SAAS,GAAG,2CAA2C,CAAC;AAC9D,MAAM,eAAe,GAAG,iDAAiD,CAAC;AAC1E,MAAM,MAAM,GAAG,+CAA+C,CAAC;AAC/D,MAAM,QAAQ,GAAG,uBAAuB,CAAC;AACzC,gHAAgH;AAChH,MAAM,KAAK,GAAG,iFAAiF,CAAC;AAEhG;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,IAAY;IACpC,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,IAAI,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,SAAS;IAC/C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,IAAY;IAC/B,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,QAAQ,GAAG,CAAC,CAAC;IACjD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACvC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAChC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAChC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC9C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrJ,OAAO,CAAC,CAAC;AACV,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,UAAqC,EAAE;IACnF,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACrD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/C,uFAAuF;IACvF,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;IACxF,MAAM,UAAU,GAAG,CAAC;SAClB,KAAK,CAAC,SAAS,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAClB,IAAI,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,kEAAkE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtH,mEAAmE;IACnE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrE,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzF,KAAK,GAAG,KAAK;SACX,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC;SAC7C,OAAO,CAAC,kCAAkC,EAAE,IAAI,CAAC;SACjD,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IACzI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,uCAAuC,EAAE,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACjG,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,UAAqC,EAAE,EAAE,WAAmB,CAAC,CAAC,mBAAmB;IAC/H,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SAC/E,KAAK,CAAC,IAAI,CAAC;SACX,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;SACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC3F,+DAA+D;IAC/D,CAAC,GAAG,CAAC;SACH,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACV,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,OAAO,KAAK,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACjF,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,4CAA4C;IAC5C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,qCAAqC,EAAE,EAAE,CAAC,CAAC;IACzD,2DAA2D;IAC3D,CAAC,GAAG,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC/D,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACjC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,uBAAuB,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IACvE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step normalizer (crewly#1247 v2): maps a runtime tool call, a browser action,
|
|
3
|
+
* a chat reply or a wait on the owner to one canvas step: a kind, a verb
|
|
4
|
+
* (`label`) and a redacted `target`. Same redaction rules as the board
|
|
5
|
+
* ({@link scrubText}, repo-relative paths, host + path, executable +
|
|
6
|
+
* subcommand); the raw tool input is read here and dropped.
|
|
7
|
+
*
|
|
8
|
+
* Pure (no I/O).
|
|
9
|
+
*
|
|
10
|
+
* @module services/board/canvas/canvas-step.normalizer
|
|
11
|
+
*/
|
|
12
|
+
import { type NormalizeContext, type ToolInput } from '../board-activity.normalizer.js';
|
|
13
|
+
import type { StepKind } from './canvas.types.js';
|
|
14
|
+
/** What a step says before it gets an id and a time. */
|
|
15
|
+
export interface StepShape {
|
|
16
|
+
kind: StepKind;
|
|
17
|
+
label: string;
|
|
18
|
+
target?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One tool call as a step.
|
|
22
|
+
*
|
|
23
|
+
* @param toolName - Runtime tool name (`Edit`, `Bash`, `mcp__x__y`, ...)
|
|
24
|
+
* @param input - Validated input slice (see {@link parseToolInput})
|
|
25
|
+
* @param ctx - Project roots for relative paths
|
|
26
|
+
* @returns Kind, verb and redacted target
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* stepOfTool('Bash', { command: 'cd app && npm test -- --ci' }); // { kind: 'command', label: 'Ran', target: 'npm test' }
|
|
31
|
+
* stepOfTool('WebSearch', { query: 'open swarm rivals' }); // { kind: 'search', label: 'Searched', target: '"open swarm rivals"' }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function stepOfTool(toolName: string, input?: ToolInput, ctx?: NormalizeContext): StepShape;
|
|
35
|
+
/**
|
|
36
|
+
* A browser action by an agent (the Chrome extension path).
|
|
37
|
+
*
|
|
38
|
+
* @param tool - Extension tool name (`navigate`, `click`, ...)
|
|
39
|
+
* @param url - The page the agent is on, if known
|
|
40
|
+
* @returns Step shape
|
|
41
|
+
*/
|
|
42
|
+
export declare function stepOfBrowserAction(tool: string, url?: string): StepShape;
|
|
43
|
+
/**
|
|
44
|
+
* An agent message to the owner or a channel.
|
|
45
|
+
*
|
|
46
|
+
* @param channel - Channel or chat name (`#general`, `owner chat`, `Slack`)
|
|
47
|
+
* @returns Step shape
|
|
48
|
+
*/
|
|
49
|
+
export declare function stepOfMessage(channel: string): StepShape;
|
|
50
|
+
/**
|
|
51
|
+
* The agent waits for the owner.
|
|
52
|
+
*
|
|
53
|
+
* @param what - Short plain-words description
|
|
54
|
+
* @returns Step shape
|
|
55
|
+
*/
|
|
56
|
+
export declare function stepOfOwnerWait(what: string): StepShape;
|
|
57
|
+
//# sourceMappingURL=canvas-step.normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-step.normalizer.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/board/canvas/canvas-step.normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAMN,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAoDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,SAAc,EAAE,GAAG,GAAE,gBAAqB,GAAG,SAAS,CAyCzG;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAYzE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAGxD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAEvD"}
|