@openagents-org/agent-launcher 0.2.192 → 0.2.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -2
- package/package.json +1 -1
- package/registry.json +9 -3
- package/src/adapters/antigravity-stream.js +65 -5
- package/src/adapters/base.js +332 -35
- package/src/adapters/claude.js +152 -21
- package/src/adapters/codex.js +37 -3
- package/src/adapters/cursor-node-entry.js +64 -0
- package/src/adapters/cursor.js +31 -1
- package/src/adapters/failure-streak.js +89 -0
- package/src/adapters/hermes.js +354 -58
- package/src/adapters/opencode.js +25 -3
- package/src/adapters/response-artifacts.js +72 -0
- package/src/adapters/thread-history.js +49 -0
- package/src/adapters/workspace-prompt.js +102 -12
- package/src/cli-models.js +244 -0
- package/src/cli.js +69 -13
- package/src/config.js +13 -3
- package/src/daemon.js +183 -50
- package/src/index.js +10 -1
- package/src/installer.js +413 -16
- package/src/log-writer.js +351 -0
- package/src/mcp-server.js +138 -9
- package/src/node-config.js +6 -4
- package/src/postinstall-stub.js +69 -0
- package/src/tools-v11/brief.js +97 -0
- package/src/tools-v11/index.js +28 -0
- package/src/tools-v11/presence-handoff.js +199 -0
- package/src/workspace-client.js +274 -32
package/src/workspace-client.js
CHANGED
|
@@ -5,6 +5,29 @@ const http = require('http');
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_ENDPOINT = 'https://workspace-endpoint.openagents.org';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* The error a request deadline produces. Every request carries a hard
|
|
10
|
+
* AbortSignal.timeout deadline (see _get), and when it fires Node rejects with
|
|
11
|
+
* a bare "AbortError: The operation was aborted" — which reads like somebody
|
|
12
|
+
* cancelled the call and names no cause. The socket-inactivity timeout said
|
|
13
|
+
* "Request timed out". Both now say the same thing, with the deadline, and
|
|
14
|
+
* carry ETIMEDOUT so callers can tell a slow network from a bug.
|
|
15
|
+
*/
|
|
16
|
+
function requestTimeoutError(timeout) {
|
|
17
|
+
const after = timeout >= 1000 ? `${Math.round(timeout / 1000)}s` : `${timeout}ms`;
|
|
18
|
+
const err = new Error(`Request timed out after ${after}`);
|
|
19
|
+
err.code = 'ETIMEDOUT';
|
|
20
|
+
return err;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A request 'error' handler that reports a fired deadline as a timeout. */
|
|
24
|
+
function rejectWithDeadline(reject, timeout) {
|
|
25
|
+
return (err) => {
|
|
26
|
+
const aborted = err && (err.name === 'AbortError' || err.code === 'ABORT_ERR');
|
|
27
|
+
reject(aborted ? requestTimeoutError(timeout) : err);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
/**
|
|
9
32
|
* Thrown when the workspace rejects a request because our session_id has
|
|
10
33
|
* been revoked by a newer /v1/join as the same agent. Callers should
|
|
@@ -18,6 +41,30 @@ class SessionRevokedError extends Error {
|
|
|
18
41
|
}
|
|
19
42
|
}
|
|
20
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Reject when a response is cut off after its headers arrived.
|
|
46
|
+
*
|
|
47
|
+
* Node reports that on the RESPONSE, and nowhere else: once a response
|
|
48
|
+
* exists the request emits no 'error' for a connection that closes early,
|
|
49
|
+
* 'end' only fires for a complete body, and the request's abort signal is a
|
|
50
|
+
* no-op by then because the socket is already gone. A request helper that
|
|
51
|
+
* listens for 'end' alone therefore never settles — and each poll loop is a
|
|
52
|
+
* single await, so one truncated response (a backend restart mid-reply is
|
|
53
|
+
* enough) silences that loop for the life of the daemon. When it is the
|
|
54
|
+
* control poller the agent keeps working and answering, and ignores Stop.
|
|
55
|
+
*/
|
|
56
|
+
function rejectOnTruncatedResponse(res, reject) {
|
|
57
|
+
res.on('error', reject);
|
|
58
|
+
res.on('close', () => {
|
|
59
|
+
if (res.complete) return;
|
|
60
|
+
// Same code Node puts on the 'error' above, so callers count either as
|
|
61
|
+
// the network blip it is.
|
|
62
|
+
const err = new Error('Response ended before it was complete');
|
|
63
|
+
err.code = 'ECONNRESET';
|
|
64
|
+
reject(err);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
21
68
|
/**
|
|
22
69
|
* HTTP client for workspace API operations.
|
|
23
70
|
*
|
|
@@ -25,8 +72,34 @@ class SessionRevokedError extends Error {
|
|
|
25
72
|
* auth headers (X-Workspace-Token), same request/response shapes.
|
|
26
73
|
*/
|
|
27
74
|
class WorkspaceClient {
|
|
28
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} [endpoint]
|
|
77
|
+
* @param {{ agentName?: string }} [opts] — when the client acts for ONE
|
|
78
|
+
* agent, pass its name: every request then carries `X-Agent-Name` so the
|
|
79
|
+
* workspace can apply that agent's permissions (permission model v1.1 §3)
|
|
80
|
+
* instead of treating the call as an anonymous machine. Machine-level
|
|
81
|
+
* clients (daemon device heartbeat, pairing redeem, workspace removal)
|
|
82
|
+
* pass nothing and send no agent identity.
|
|
83
|
+
*/
|
|
84
|
+
constructor(endpoint, { agentName } = {}) {
|
|
29
85
|
this.endpoint = (endpoint || DEFAULT_ENDPOINT).replace(/\/$/, '');
|
|
86
|
+
this.agentName = typeof agentName === 'string' && agentName.trim() ? agentName.trim() : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A client for the same endpoint that acts as `agentName` (or none). */
|
|
90
|
+
withAgent(agentName) {
|
|
91
|
+
return new WorkspaceClient(this.endpoint, { agentName });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Request headers + the agent identity header. Applied in every transport
|
|
96
|
+
* helper (_get/_getRaw/_post/_put/_patch/_delete) so no call site can forget
|
|
97
|
+
* it. Returns a copy — callers' header objects are never mutated.
|
|
98
|
+
*/
|
|
99
|
+
_withAgent(headers) {
|
|
100
|
+
const out = { ...(headers || {}) };
|
|
101
|
+
if (this.agentName && !out['X-Agent-Name']) out['X-Agent-Name'] = this.agentName;
|
|
102
|
+
return out;
|
|
30
103
|
}
|
|
31
104
|
|
|
32
105
|
/**
|
|
@@ -194,16 +267,80 @@ class WorkspaceClient {
|
|
|
194
267
|
* If the server's current session for this agent differs, _post()
|
|
195
268
|
* throws SessionRevokedError and the caller should stop its adapter.
|
|
196
269
|
*/
|
|
197
|
-
async heartbeat(workspaceId, agentName, token, sessionId) {
|
|
270
|
+
async heartbeat(workspaceId, agentName, token, sessionId, presence) {
|
|
198
271
|
const body = {
|
|
199
272
|
agent_name: agentName,
|
|
200
273
|
network: workspaceId,
|
|
201
274
|
};
|
|
202
275
|
if (sessionId) body.session_id = sessionId;
|
|
276
|
+
// v1.1 M3 — busy/queued detail ({ presence_state, busy_channels,
|
|
277
|
+
// queue_depth }, see BaseAdapter.buildPresenceFields). Optional so older
|
|
278
|
+
// callers keep sending the legacy body.
|
|
279
|
+
if (presence && typeof presence === 'object') {
|
|
280
|
+
if (presence.presence_state !== undefined) body.presence_state = presence.presence_state;
|
|
281
|
+
if (Array.isArray(presence.busy_channels)) body.busy_channels = presence.busy_channels;
|
|
282
|
+
if (Number.isFinite(presence.queue_depth)) body.queue_depth = presence.queue_depth;
|
|
283
|
+
}
|
|
203
284
|
const data = await this._post('/v1/heartbeat', body, this._wsHeaders(token));
|
|
204
285
|
return data.data || data;
|
|
205
286
|
}
|
|
206
287
|
|
|
288
|
+
// ── Agent profile / presence (v1.1 M3/M4) ────────────────────────────────
|
|
289
|
+
|
|
290
|
+
/** GET /v1/agents/{agent}/availability → { status, presence_state,
|
|
291
|
+
* busy_channels, queue_depth, runtime_status, runtime_name, reason }. */
|
|
292
|
+
async getAgentAvailability(workspaceId, token, agentName) {
|
|
293
|
+
const data = await this._get(
|
|
294
|
+
`/v1/agents/${encodeURIComponent(agentName)}/availability?network=${encodeURIComponent(workspaceId)}`,
|
|
295
|
+
this._wsHeaders(token),
|
|
296
|
+
);
|
|
297
|
+
return data.data || data;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** GET /v1/agents/{agent}/profile — the specialist card. */
|
|
301
|
+
async getAgentProfile(workspaceId, token, agentName) {
|
|
302
|
+
const data = await this._get(
|
|
303
|
+
`/v1/agents/${encodeURIComponent(agentName)}/profile?network=${encodeURIComponent(workspaceId)}`,
|
|
304
|
+
this._wsHeaders(token),
|
|
305
|
+
);
|
|
306
|
+
return data.data || data;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* GET /v1/agents/{agent}/shared-context — what THIS agent's run may use when
|
|
311
|
+
* the request comes from a teammate rather than its owner. Machine-only.
|
|
312
|
+
* Returns { apply, owner_email, requester_email, shared_instructions,
|
|
313
|
+
* allowed_knowledge: [{ slug, title, content }], cost_owner }.
|
|
314
|
+
*/
|
|
315
|
+
async getSharedContext(workspaceId, token, agentName, { requesterEmail, channel, timeout } = {}) {
|
|
316
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
317
|
+
if (requesterEmail) params.set('requester_email', requesterEmail);
|
|
318
|
+
if (channel) params.set('channel', channel);
|
|
319
|
+
const data = await this._get(
|
|
320
|
+
`/v1/agents/${encodeURIComponent(agentName)}/shared-context?${params}`,
|
|
321
|
+
this._wsHeaders(token),
|
|
322
|
+
timeout || 5000,
|
|
323
|
+
);
|
|
324
|
+
return data.data || data;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Raw message events of a channel (newest first by default) — for callers
|
|
329
|
+
* that need payload/metadata fields _eventToMessage drops (e.g. hand-offs).
|
|
330
|
+
*/
|
|
331
|
+
async getChannelEvents(workspaceId, channelName, token, { limit = 100, sort = 'desc' } = {}) {
|
|
332
|
+
const params = new URLSearchParams({
|
|
333
|
+
network: workspaceId,
|
|
334
|
+
channel: channelName,
|
|
335
|
+
type: 'workspace.message',
|
|
336
|
+
sort,
|
|
337
|
+
limit: String(Math.min(Math.max(1, limit), 500)),
|
|
338
|
+
});
|
|
339
|
+
const data = await this._get(`/v1/events?${params}`, this._wsHeaders(token));
|
|
340
|
+
const result = data.data || data;
|
|
341
|
+
return (result && result.events) || [];
|
|
342
|
+
}
|
|
343
|
+
|
|
207
344
|
/**
|
|
208
345
|
* Disconnect agent via POST /v1/leave. Best-effort (ignores errors).
|
|
209
346
|
*/
|
|
@@ -300,7 +437,7 @@ class WorkspaceClient {
|
|
|
300
437
|
* previous session fails (the channel's chat history is the only thing
|
|
301
438
|
* that survives a session-storage rotation).
|
|
302
439
|
*/
|
|
303
|
-
async getRecentMessages(workspaceId, channelName, token, limit = 30, { sort = 'desc' } = {}) {
|
|
440
|
+
async getRecentMessages(workspaceId, channelName, token, limit = 30, { sort = 'desc', before, timeout, throwOnError = false } = {}) {
|
|
304
441
|
try {
|
|
305
442
|
const params = new URLSearchParams({
|
|
306
443
|
network: workspaceId,
|
|
@@ -309,14 +446,16 @@ class WorkspaceClient {
|
|
|
309
446
|
sort,
|
|
310
447
|
limit: String(limit),
|
|
311
448
|
});
|
|
312
|
-
|
|
449
|
+
if (before) params.set('before', before);
|
|
450
|
+
const data = await this._get(`/v1/events?${params}`, this._wsHeaders(token), timeout);
|
|
313
451
|
const result = data.data || data;
|
|
314
452
|
const events = (result && result.events) || [];
|
|
315
453
|
// A desc window arrives newest-first; reverse so the caller always gets
|
|
316
454
|
// chronological order. An asc window is already chronological.
|
|
317
455
|
const ordered = sort === 'asc' ? events.slice() : events.slice().reverse();
|
|
318
456
|
return ordered.map((e) => this._eventToMessage(e));
|
|
319
|
-
} catch {
|
|
457
|
+
} catch (error) {
|
|
458
|
+
if (throwOnError) throw error;
|
|
320
459
|
return [];
|
|
321
460
|
}
|
|
322
461
|
}
|
|
@@ -564,8 +703,17 @@ class WorkspaceClient {
|
|
|
564
703
|
/**
|
|
565
704
|
* Upload a file via POST /v1/files/base64.
|
|
566
705
|
*/
|
|
706
|
+
async publishWebProject(workspaceId, token, project, projectId) {
|
|
707
|
+
const body = { ...project, network: workspaceId };
|
|
708
|
+
const route = '/v1/web-projects' + (projectId ? '/' + encodeURIComponent(projectId) : '');
|
|
709
|
+
const data = projectId
|
|
710
|
+
? await this._put(route, body, this._wsHeaders(token), 60000)
|
|
711
|
+
: await this._post(route, body, this._wsHeaders(token), 60000);
|
|
712
|
+
return data.data || data;
|
|
713
|
+
}
|
|
714
|
+
|
|
567
715
|
async uploadFile(workspaceId, token, filename, contentBase64, {
|
|
568
|
-
contentType = 'application/octet-stream', source = 'human:user', channelName,
|
|
716
|
+
contentType = 'application/octet-stream', source = 'human:user', channelName, visibility,
|
|
569
717
|
} = {}) {
|
|
570
718
|
const body = {
|
|
571
719
|
filename,
|
|
@@ -575,6 +723,7 @@ class WorkspaceClient {
|
|
|
575
723
|
source,
|
|
576
724
|
};
|
|
577
725
|
if (channelName) body.channel_name = channelName;
|
|
726
|
+
if (visibility) body.visibility = visibility;
|
|
578
727
|
|
|
579
728
|
const data = await this._post('/v1/files/base64', body, this._wsHeaders(token), 60000);
|
|
580
729
|
return data.data || data;
|
|
@@ -652,8 +801,8 @@ class WorkspaceClient {
|
|
|
652
801
|
/**
|
|
653
802
|
* Open a new browser tab via POST /v1/browser/tabs.
|
|
654
803
|
*/
|
|
655
|
-
async browserOpenTab(workspaceId, token, { url = 'about:blank', source = 'human:user', context_id } = {}) {
|
|
656
|
-
const body = { url, network: workspaceId, source };
|
|
804
|
+
async browserOpenTab(workspaceId, token, { url = 'about:blank', source = 'human:user', context_id, channel } = {}) {
|
|
805
|
+
const body = { url, network: workspaceId, source, channel };
|
|
657
806
|
if (context_id) body.context_id = context_id;
|
|
658
807
|
const data = await this._post('/v1/browser/tabs', body, this._wsHeaders(token));
|
|
659
808
|
return data.data || data;
|
|
@@ -671,24 +820,24 @@ class WorkspaceClient {
|
|
|
671
820
|
/**
|
|
672
821
|
* Navigate a browser tab via POST /v1/browser/tabs/{tabId}/navigate.
|
|
673
822
|
*/
|
|
674
|
-
async browserNavigate(workspaceId, token, tabId, url) {
|
|
675
|
-
const data = await this._post(`/v1/browser/tabs/${tabId}/navigate`, { url }, this._wsHeaders(token));
|
|
823
|
+
async browserNavigate(workspaceId, token, tabId, url, { source, channel } = {}) {
|
|
824
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/navigate`, { url, source, channel }, this._wsHeaders(token));
|
|
676
825
|
return data.data || data;
|
|
677
826
|
}
|
|
678
827
|
|
|
679
828
|
/**
|
|
680
829
|
* Click an element via POST /v1/browser/tabs/{tabId}/click.
|
|
681
830
|
*/
|
|
682
|
-
async browserClick(workspaceId, token, tabId, selector) {
|
|
683
|
-
const data = await this._post(`/v1/browser/tabs/${tabId}/click`, { selector }, this._wsHeaders(token));
|
|
831
|
+
async browserClick(workspaceId, token, tabId, selector, { source, channel } = {}) {
|
|
832
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/click`, { selector, source, channel }, this._wsHeaders(token));
|
|
684
833
|
return data.data || data;
|
|
685
834
|
}
|
|
686
835
|
|
|
687
836
|
/**
|
|
688
837
|
* Type text via POST /v1/browser/tabs/{tabId}/type.
|
|
689
838
|
*/
|
|
690
|
-
async browserType(workspaceId, token, tabId, selector, text) {
|
|
691
|
-
const data = await this._post(`/v1/browser/tabs/${tabId}/type`, { selector, text }, this._wsHeaders(token));
|
|
839
|
+
async browserType(workspaceId, token, tabId, selector, text, { source, channel } = {}) {
|
|
840
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/type`, { selector, text, source, channel }, this._wsHeaders(token));
|
|
692
841
|
return data.data || data;
|
|
693
842
|
}
|
|
694
843
|
|
|
@@ -696,16 +845,22 @@ class WorkspaceClient {
|
|
|
696
845
|
* Get screenshot via GET /v1/browser/tabs/{tabId}/screenshot.
|
|
697
846
|
* @returns {Buffer}
|
|
698
847
|
*/
|
|
699
|
-
async browserScreenshot(workspaceId, token, tabId) {
|
|
700
|
-
|
|
848
|
+
async browserScreenshot(workspaceId, token, tabId, { source, channel } = {}) {
|
|
849
|
+
const query = new URLSearchParams();
|
|
850
|
+
if (source) query.set('source', source);
|
|
851
|
+
if (channel) query.set('channel', channel);
|
|
852
|
+
return this._getRaw(`/v1/browser/tabs/${tabId}/screenshot?${query}`, this._wsHeaders(token), 30000);
|
|
701
853
|
}
|
|
702
854
|
|
|
703
855
|
/**
|
|
704
856
|
* Get accessibility snapshot via GET /v1/browser/tabs/{tabId}/snapshot.
|
|
705
857
|
* @returns {string}
|
|
706
858
|
*/
|
|
707
|
-
async browserSnapshot(workspaceId, token, tabId) {
|
|
708
|
-
const
|
|
859
|
+
async browserSnapshot(workspaceId, token, tabId, { source, channel } = {}) {
|
|
860
|
+
const query = new URLSearchParams();
|
|
861
|
+
if (source) query.set('source', source);
|
|
862
|
+
if (channel) query.set('channel', channel);
|
|
863
|
+
const buf = await this._getRaw(`/v1/browser/tabs/${tabId}/snapshot?${query}`, this._wsHeaders(token));
|
|
709
864
|
return buf.toString('utf-8');
|
|
710
865
|
}
|
|
711
866
|
|
|
@@ -920,11 +1075,25 @@ class WorkspaceClient {
|
|
|
920
1075
|
sessionId,
|
|
921
1076
|
senderType: isHuman ? 'human' : 'agent',
|
|
922
1077
|
senderName,
|
|
1078
|
+
senderDisplayName: payload.sender_display_name || (event.metadata || {}).sender_display_name || null,
|
|
923
1079
|
content: (payload.content || event.content || ''),
|
|
924
1080
|
mentions: payload.mentions || [],
|
|
925
1081
|
messageType: payload.message_type || 'chat',
|
|
926
1082
|
metadata: event.metadata || {},
|
|
927
1083
|
};
|
|
1084
|
+
// The person behind a human message (v1.1): mobile puts it in the
|
|
1085
|
+
// payload, the Launcher in metadata, the web app uses its email as the
|
|
1086
|
+
// sender id. Used to fetch the owner-reviewed shared context for a
|
|
1087
|
+
// teammate's request and to tell the owner who asked.
|
|
1088
|
+
if (isHuman) {
|
|
1089
|
+
const meta = event.metadata || {};
|
|
1090
|
+
const email = [payload.sender_email, meta.sender_email, payload.sender_id]
|
|
1091
|
+
.find((v) => typeof v === 'string' && /^[^\s@]+@[^\s@]+$/.test(v.trim()));
|
|
1092
|
+
if (email) msg.senderEmail = email.trim().toLowerCase();
|
|
1093
|
+
}
|
|
1094
|
+
if (payload.handoff && typeof payload.handoff === 'object') {
|
|
1095
|
+
msg.handoff = payload.handoff;
|
|
1096
|
+
}
|
|
928
1097
|
if (ts) {
|
|
929
1098
|
msg.createdAt = new Date(ts).toISOString();
|
|
930
1099
|
}
|
|
@@ -934,6 +1103,60 @@ class WorkspaceClient {
|
|
|
934
1103
|
return msg;
|
|
935
1104
|
}
|
|
936
1105
|
|
|
1106
|
+
// ── Approvals ─────────────────────────────────────────────────────────────
|
|
1107
|
+
|
|
1108
|
+
/** File an approval request (POST /v1/approvals). Returns the record; its
|
|
1109
|
+
* `status` may already be approved/rejected when policy auto-decided. */
|
|
1110
|
+
async createApproval(workspaceId, token, {
|
|
1111
|
+
channel, kind, action, details, risk, source, assignee_email, requester_email,
|
|
1112
|
+
} = {}) {
|
|
1113
|
+
const body = {
|
|
1114
|
+
network: workspaceId,
|
|
1115
|
+
channel,
|
|
1116
|
+
kind: kind || 'other',
|
|
1117
|
+
action,
|
|
1118
|
+
source: source || 'openagents:unknown',
|
|
1119
|
+
};
|
|
1120
|
+
if (details) body.details = String(details).slice(0, 4000);
|
|
1121
|
+
if (risk) body.risk = risk;
|
|
1122
|
+
if (assignee_email) body.assignee_email = assignee_email;
|
|
1123
|
+
if (requester_email) body.requester_email = requester_email;
|
|
1124
|
+
const data = await this._post('/v1/approvals', body, this._wsHeaders(token));
|
|
1125
|
+
return data.data || data;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async getApproval(workspaceId, token, approvalId) {
|
|
1129
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
1130
|
+
const data = await this._get(`/v1/approvals/${encodeURIComponent(approvalId)}?${params}`, this._wsHeaders(token));
|
|
1131
|
+
return data.data || data;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// ── Work brief (v1.1 M5) ──────────────────────────────────────────────────
|
|
1135
|
+
|
|
1136
|
+
/** Read a thread's shared work brief (GET /v1/channels/{channel}/brief).
|
|
1137
|
+
* Empty fields come back null / [] when nothing has been written yet. */
|
|
1138
|
+
async getBrief(workspaceId, token, channelName) {
|
|
1139
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
1140
|
+
const data = await this._get(
|
|
1141
|
+
`/v1/channels/${encodeURIComponent(channelName)}/brief?${params}`,
|
|
1142
|
+
this._wsHeaders(token),
|
|
1143
|
+
);
|
|
1144
|
+
return data.data || data;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/** Partial upsert of a thread's brief (PUT /v1/channels/{channel}/brief).
|
|
1148
|
+
* Only the keys present in `fields` change; `source` is recorded as the
|
|
1149
|
+
* writer ("openagents:<agent>"). */
|
|
1150
|
+
async putBrief(workspaceId, token, channelName, fields = {}, { source } = {}) {
|
|
1151
|
+
const body = { network: workspaceId, source: source || 'openagents:unknown', ...fields };
|
|
1152
|
+
const data = await this._put(
|
|
1153
|
+
`/v1/channels/${encodeURIComponent(channelName)}/brief`,
|
|
1154
|
+
body,
|
|
1155
|
+
this._wsHeaders(token),
|
|
1156
|
+
);
|
|
1157
|
+
return data.data || data;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
937
1160
|
_wsHeaders(token) {
|
|
938
1161
|
return {
|
|
939
1162
|
'Content-Type': 'application/json',
|
|
@@ -942,6 +1165,7 @@ class WorkspaceClient {
|
|
|
942
1165
|
}
|
|
943
1166
|
|
|
944
1167
|
_get(urlPath, headers = {}, timeout = 15000) {
|
|
1168
|
+
headers = this._withAgent(headers);
|
|
945
1169
|
const fullUrl = this.endpoint + urlPath;
|
|
946
1170
|
|
|
947
1171
|
return new Promise((resolve, reject) => {
|
|
@@ -962,28 +1186,37 @@ class WorkspaceClient {
|
|
|
962
1186
|
signal: AbortSignal.timeout(timeout),
|
|
963
1187
|
}, (res) => {
|
|
964
1188
|
let data = '';
|
|
1189
|
+
rejectOnTruncatedResponse(res, reject);
|
|
965
1190
|
res.on('data', (chunk) => { data += chunk; });
|
|
966
1191
|
res.on('end', () => {
|
|
967
1192
|
try {
|
|
968
1193
|
const parsed = JSON.parse(data);
|
|
969
1194
|
if (res.statusCode >= 400) {
|
|
970
|
-
|
|
1195
|
+
const err = new Error(parsed.message || `HTTP ${res.statusCode}`);
|
|
1196
|
+
// Lets the poll loop tell a rejected credential (401/403) and an
|
|
1197
|
+
// overloaded server (5xx) apart from a bug.
|
|
1198
|
+
err.status = res.statusCode;
|
|
1199
|
+
reject(err);
|
|
971
1200
|
} else {
|
|
972
1201
|
resolve(parsed);
|
|
973
1202
|
}
|
|
974
1203
|
} catch {
|
|
975
|
-
|
|
1204
|
+
// A proxy's HTML error page (502/504) lands here.
|
|
1205
|
+
const err = new Error(`Invalid response: ${data.slice(0, 200)}`);
|
|
1206
|
+
if (res.statusCode >= 400) err.status = res.statusCode;
|
|
1207
|
+
reject(err);
|
|
976
1208
|
}
|
|
977
1209
|
});
|
|
978
1210
|
});
|
|
979
1211
|
|
|
980
|
-
req.on('error', reject);
|
|
981
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1212
|
+
req.on('error', rejectWithDeadline(reject, timeout));
|
|
1213
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
|
|
982
1214
|
req.end();
|
|
983
1215
|
});
|
|
984
1216
|
}
|
|
985
1217
|
|
|
986
1218
|
_getRaw(urlPath, headers = {}, timeout = 15000) {
|
|
1219
|
+
headers = this._withAgent(headers);
|
|
987
1220
|
const fullUrl = this.endpoint + urlPath;
|
|
988
1221
|
|
|
989
1222
|
return new Promise((resolve, reject) => {
|
|
@@ -997,6 +1230,7 @@ class WorkspaceClient {
|
|
|
997
1230
|
signal: AbortSignal.timeout(timeout), // hard deadline (covers DNS/connect); see _get
|
|
998
1231
|
}, (res) => {
|
|
999
1232
|
const chunks = [];
|
|
1233
|
+
rejectOnTruncatedResponse(res, reject);
|
|
1000
1234
|
res.on('data', (chunk) => { chunks.push(chunk); });
|
|
1001
1235
|
res.on('end', () => {
|
|
1002
1236
|
const buf = Buffer.concat(chunks);
|
|
@@ -1008,13 +1242,14 @@ class WorkspaceClient {
|
|
|
1008
1242
|
});
|
|
1009
1243
|
});
|
|
1010
1244
|
|
|
1011
|
-
req.on('error', reject);
|
|
1012
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1245
|
+
req.on('error', rejectWithDeadline(reject, timeout));
|
|
1246
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
|
|
1013
1247
|
req.end();
|
|
1014
1248
|
});
|
|
1015
1249
|
}
|
|
1016
1250
|
|
|
1017
1251
|
_post(urlPath, body, headers = {}, timeout = 30000) {
|
|
1252
|
+
headers = this._withAgent(headers);
|
|
1018
1253
|
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
|
|
1019
1254
|
const jsonBody = JSON.stringify(body);
|
|
1020
1255
|
const fullUrl = this.endpoint + urlPath;
|
|
@@ -1030,6 +1265,7 @@ class WorkspaceClient {
|
|
|
1030
1265
|
signal: AbortSignal.timeout(timeout), // hard deadline (covers DNS/connect); see _get
|
|
1031
1266
|
}, (res) => {
|
|
1032
1267
|
let data = '';
|
|
1268
|
+
rejectOnTruncatedResponse(res, reject);
|
|
1033
1269
|
res.on('data', (chunk) => { data += chunk; });
|
|
1034
1270
|
res.on('end', () => {
|
|
1035
1271
|
try {
|
|
@@ -1058,14 +1294,15 @@ class WorkspaceClient {
|
|
|
1058
1294
|
});
|
|
1059
1295
|
});
|
|
1060
1296
|
|
|
1061
|
-
req.on('error', reject);
|
|
1062
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1297
|
+
req.on('error', rejectWithDeadline(reject, timeout));
|
|
1298
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
|
|
1063
1299
|
req.write(jsonBody);
|
|
1064
1300
|
req.end();
|
|
1065
1301
|
});
|
|
1066
1302
|
}
|
|
1067
1303
|
|
|
1068
1304
|
_put(urlPath, body, headers = {}, timeout = 30000) {
|
|
1305
|
+
headers = this._withAgent(headers);
|
|
1069
1306
|
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
|
|
1070
1307
|
const jsonBody = JSON.stringify(body);
|
|
1071
1308
|
const fullUrl = this.endpoint + urlPath;
|
|
@@ -1081,6 +1318,7 @@ class WorkspaceClient {
|
|
|
1081
1318
|
signal: AbortSignal.timeout(timeout), // hard deadline (covers DNS/connect); see _get
|
|
1082
1319
|
}, (res) => {
|
|
1083
1320
|
let data = '';
|
|
1321
|
+
rejectOnTruncatedResponse(res, reject);
|
|
1084
1322
|
res.on('data', (chunk) => { data += chunk; });
|
|
1085
1323
|
res.on('end', () => {
|
|
1086
1324
|
try {
|
|
@@ -1101,14 +1339,15 @@ class WorkspaceClient {
|
|
|
1101
1339
|
});
|
|
1102
1340
|
});
|
|
1103
1341
|
|
|
1104
|
-
req.on('error', reject);
|
|
1105
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1342
|
+
req.on('error', rejectWithDeadline(reject, timeout));
|
|
1343
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
|
|
1106
1344
|
req.write(jsonBody);
|
|
1107
1345
|
req.end();
|
|
1108
1346
|
});
|
|
1109
1347
|
}
|
|
1110
1348
|
|
|
1111
1349
|
_patch(urlPath, body, headers = {}, timeout = 15000) {
|
|
1350
|
+
headers = this._withAgent(headers);
|
|
1112
1351
|
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
|
|
1113
1352
|
const jsonBody = JSON.stringify(body);
|
|
1114
1353
|
const fullUrl = this.endpoint + urlPath;
|
|
@@ -1124,6 +1363,7 @@ class WorkspaceClient {
|
|
|
1124
1363
|
signal: AbortSignal.timeout(timeout), // hard deadline (covers DNS/connect); see _get
|
|
1125
1364
|
}, (res) => {
|
|
1126
1365
|
let data = '';
|
|
1366
|
+
rejectOnTruncatedResponse(res, reject);
|
|
1127
1367
|
res.on('data', (chunk) => { data += chunk; });
|
|
1128
1368
|
res.on('end', () => {
|
|
1129
1369
|
try {
|
|
@@ -1139,14 +1379,15 @@ class WorkspaceClient {
|
|
|
1139
1379
|
});
|
|
1140
1380
|
});
|
|
1141
1381
|
|
|
1142
|
-
req.on('error', reject);
|
|
1143
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1382
|
+
req.on('error', rejectWithDeadline(reject, timeout));
|
|
1383
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
|
|
1144
1384
|
req.write(jsonBody);
|
|
1145
1385
|
req.end();
|
|
1146
1386
|
});
|
|
1147
1387
|
}
|
|
1148
1388
|
|
|
1149
1389
|
_delete(urlPath, headers = {}, network) {
|
|
1390
|
+
headers = this._withAgent(headers);
|
|
1150
1391
|
const fullUrl = this.endpoint + urlPath + (network ? `?network=${network}` : '');
|
|
1151
1392
|
|
|
1152
1393
|
return new Promise((resolve, reject) => {
|
|
@@ -1160,6 +1401,7 @@ class WorkspaceClient {
|
|
|
1160
1401
|
signal: AbortSignal.timeout(15000), // hard deadline (covers DNS/connect); see _get
|
|
1161
1402
|
}, (res) => {
|
|
1162
1403
|
let data = '';
|
|
1404
|
+
rejectOnTruncatedResponse(res, reject);
|
|
1163
1405
|
res.on('data', (chunk) => { data += chunk; });
|
|
1164
1406
|
res.on('end', () => {
|
|
1165
1407
|
try {
|
|
@@ -1175,8 +1417,8 @@ class WorkspaceClient {
|
|
|
1175
1417
|
});
|
|
1176
1418
|
});
|
|
1177
1419
|
|
|
1178
|
-
req.on('error', reject);
|
|
1179
|
-
req.on('timeout', () => { req.destroy(); reject(
|
|
1420
|
+
req.on('error', rejectWithDeadline(reject, 15000));
|
|
1421
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(15000)); });
|
|
1180
1422
|
req.end();
|
|
1181
1423
|
});
|
|
1182
1424
|
}
|