@ours.network/fleet 0.18.0-nightly.5 → 0.18.0

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.
Files changed (65) hide show
  1. package/README.md +101 -25
  2. package/dist/application/fleet-query-service.js +12 -0
  3. package/dist/application/role-creation-service.js +3 -1
  4. package/dist/application/types.d.ts +11 -0
  5. package/dist/briefing.js +9 -2
  6. package/dist/build-info.json +7 -6
  7. package/dist/capabilities.d.ts +3 -1
  8. package/dist/capabilities.js +3 -0
  9. package/dist/cli.js +83 -7
  10. package/dist/config.d.ts +11 -3
  11. package/dist/config.js +40 -15
  12. package/dist/creation.d.ts +14 -15
  13. package/dist/creation.js +19 -13
  14. package/dist/docs.d.ts +1 -1
  15. package/dist/docs.js +113 -27
  16. package/dist/doctor.d.ts +1 -5
  17. package/dist/doctor.js +11 -18
  18. package/dist/fleet-proxy.d.ts +5 -0
  19. package/dist/harness/acp-agent.js +11 -6
  20. package/dist/harness/claude-code.js +204 -11
  21. package/dist/harness/codex.d.ts +4 -1
  22. package/dist/harness/codex.js +74 -12
  23. package/dist/harness/types.d.ts +54 -4
  24. package/dist/harness-plugins.d.ts +48 -0
  25. package/dist/harness-plugins.js +309 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +1 -0
  28. package/dist/loops/manager.d.ts +30 -1
  29. package/dist/loops/manager.js +69 -6
  30. package/dist/loops/state.d.ts +18 -0
  31. package/dist/loops/state.js +4 -0
  32. package/dist/model-env.d.ts +71 -0
  33. package/dist/model-env.js +106 -0
  34. package/dist/monitor.js +1 -1
  35. package/dist/ops.js +1 -1
  36. package/dist/owner-channel/attachments.d.ts +2 -25
  37. package/dist/owner-channel/attachments.js +5 -61
  38. package/dist/owner-channel/channel.d.ts +28 -17
  39. package/dist/owner-channel/channel.js +249 -158
  40. package/dist/owner-channel/mcp.d.ts +24 -0
  41. package/dist/owner-channel/mcp.js +145 -0
  42. package/dist/owner-channel/notices.d.ts +7 -0
  43. package/dist/owner-channel/notices.js +9 -0
  44. package/dist/resolved-plan.js +1 -0
  45. package/dist/runner.d.ts +48 -0
  46. package/dist/runner.js +237 -85
  47. package/dist/session/acp.d.ts +104 -0
  48. package/dist/session/acp.js +213 -10
  49. package/dist/session/activity.d.ts +31 -0
  50. package/dist/session/activity.js +48 -0
  51. package/dist/session/conversation-normalizer.d.ts +6 -0
  52. package/dist/session/conversation-normalizer.js +153 -10
  53. package/dist/session/conversation-types.d.ts +23 -4
  54. package/dist/session/types.d.ts +35 -0
  55. package/dist/spawn.js +29 -17
  56. package/dist/supervisor/systemd.js +2 -29
  57. package/dist/watchdog/briefing.js +7 -0
  58. package/dist/web-app/assets/{TerminalView-BAVk1Bot.js → TerminalView-C_G1ID2P.js} +1 -1
  59. package/dist/web-app/assets/{index-C3S-xFRU.js → index-BCBK78hw.js} +5 -5
  60. package/dist/web-app/index.html +1 -1
  61. package/dist/worklog.d.ts +7 -1
  62. package/dist/worklog.js +191 -39
  63. package/package.json +1 -3
  64. package/dist/owner-channel/ours-client.d.ts +0 -141
  65. package/dist/owner-channel/ours-client.js +0 -225
@@ -1,225 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { readFile } from 'node:fs/promises';
3
- import { extname } from 'node:path';
4
- import { OursError, attachOursClient, } from '@ours.network/sdk/client';
5
- /** Any failure of a daemon operation. Never carries a message body or a token. */
6
- export class OursDaemonError extends Error {
7
- }
8
- /**
9
- * The daemon accepted the call and answered "not sent". The MCP surface reported
10
- * exactly these verdicts as tool errors, so they must keep throwing here: the
11
- * owner channel books a resolved send as delivered, and a silently-swallowed
12
- * refusal would be recorded as a delivered message that never left the host.
13
- */
14
- export class OursSendRefusedError extends OursDaemonError {
15
- }
16
- // The daemon normally returns a quiet long-poll within 25 seconds. Keep the
17
- // client-side fence comfortably above it so ordinary quiet periods do not
18
- // recycle a healthy stream, while a half-open socket still heals on its own.
19
- const NOTIFICATION_REQUEST_DEADLINE_MS = 120_000;
20
- // SDK sendFile({path}) inferred these common types before reading the path in
21
- // the daemon process. Staged uploads move that read into fleet, so preserve the
22
- // same advertised MIME rather than silently turning every attachment into an
23
- // octet stream. Importing the SDK root just for its helper would also pull the
24
- // daemon runtime into this client-only process.
25
- const FILE_MIME_BY_EXTENSION = {
26
- '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
27
- '.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', '.ico': 'image/x-icon',
28
- '.pdf': 'application/pdf', '.txt': 'text/plain', '.md': 'text/markdown',
29
- '.json': 'application/json', '.csv': 'text/csv', '.html': 'text/html',
30
- '.xml': 'application/xml', '.zip': 'application/zip', '.gz': 'application/gzip',
31
- '.tar': 'application/x-tar', '.mp3': 'audio/mpeg', '.wav': 'audio/wav',
32
- '.mp4': 'video/mp4', '.mov': 'video/quicktime',
33
- };
34
- function mimeFromFilename(filename) {
35
- return FILE_MIME_BY_EXTENSION[extname(filename).toLowerCase()] ?? 'application/octet-stream';
36
- }
37
- export class OursWatchDeadlineError extends OursDaemonError {
38
- }
39
- function notificationRequest(input) {
40
- const url = typeof input === 'string' || input instanceof URL ? String(input) : input.url;
41
- return /\/identities\/[^/]+\/notifications(?:\?|$)/.test(url);
42
- }
43
- function notificationDeadlineFetch(fetchImpl, deadlineMs) {
44
- return async (input, init) => {
45
- if (!notificationRequest(input))
46
- return fetchImpl(input, init);
47
- const ctrl = new AbortController();
48
- const upstream = init?.signal;
49
- let rejectFence;
50
- const fence = new Promise((_resolve, reject) => { rejectFence = reject; });
51
- const abortUpstream = () => {
52
- const reason = upstream?.reason
53
- ?? new DOMException('ours notification request aborted', 'AbortError');
54
- ctrl.abort(reason);
55
- // Shutdown must not wait for the deadline when a custom fetch ignores
56
- // AbortSignal either.
57
- rejectFence(reason);
58
- };
59
- if (upstream?.aborted)
60
- abortUpstream();
61
- else
62
- upstream?.addEventListener('abort', abortUpstream, { once: true });
63
- const timer = setTimeout(() => {
64
- const error = new OursWatchDeadlineError('ours notification request deadline exceeded');
65
- ctrl.abort(error);
66
- // Do not rely on a custom or half-open fetch implementation to honor
67
- // AbortSignal: the fence itself must always settle the request.
68
- rejectFence(error);
69
- }, deadlineMs);
70
- timer.unref?.();
71
- try {
72
- return await Promise.race([
73
- fetchImpl(input, { ...init, signal: ctrl.signal }),
74
- fence,
75
- ]);
76
- }
77
- finally {
78
- clearTimeout(timer);
79
- upstream?.removeEventListener('abort', abortUpstream);
80
- }
81
- };
82
- }
83
- /**
84
- * The typed error code of a daemon operation, or undefined when the failure was
85
- * not one (transport, abort, programming error). `instanceof` is checked first;
86
- * the structural fallback keeps a duplicated SDK copy in a consumer's tree from
87
- * silently demoting a real daemon verdict to "unknown transport failure".
88
- */
89
- export function oursErrorCode(error) {
90
- if (error instanceof OursError)
91
- return error.code;
92
- if (error instanceof Error && error.name === 'OursError') {
93
- const code = error.code;
94
- if (typeof code === 'string')
95
- return code;
96
- }
97
- return undefined;
98
- }
99
- /** The identity is bound by another live session; a predecessor may still be releasing it. */
100
- export const OURS_BOUND_ELSEWHERE = 'BOUND_ELSEWHERE';
101
- /**
102
- * The owner-channel's daemon client: one `OursClient` over the local ours HTTP
103
- * API, owning exactly one identity binding.
104
- *
105
- * Lease lifetime. The lease token IS the session, so each channel instance mints
106
- * its own and hands it back in `close()`. That replaces the connector proxy's
107
- * shell-PID fence, which existed because a supervised attempt had to make its
108
- * lease reclaimable while the supervisor itself stayed alive: an explicit
109
- * release does that deterministically, and `clientPid` still covers the case
110
- * where the whole supervisor dies without unwinding.
111
- */
112
- export class OursSdkClient {
113
- env;
114
- log;
115
- deps;
116
- client;
117
- leaseToken = `ours-fleet-owner-${process.pid}-${randomUUID()}`;
118
- constructor(env = {}, log = () => undefined, deps = {}) {
119
- this.env = env;
120
- this.log = log;
121
- this.deps = deps;
122
- }
123
- async start() {
124
- if (this.client)
125
- return;
126
- const environment = { ...process.env, ...this.env };
127
- // SDK 2's supported application path resolves endpoint, state root, and
128
- // token as one coherent selection, proves the daemon's state root before
129
- // sending credentials, and only then constructs the client.
130
- const options = {
131
- env: environment,
132
- leaseToken: this.leaseToken,
133
- clientPid: process.pid,
134
- fetch: notificationDeadlineFetch(this.deps.fetch ?? globalThis.fetch, this.deps.notificationRequestDeadlineMs ?? NOTIFICATION_REQUEST_DEADLINE_MS),
135
- };
136
- this.client = await (this.deps.attachClient?.(options) ?? attachOursClient(options));
137
- }
138
- async bindIdentity(name) {
139
- // force is pinned off: the owner channel never evicts another live session
140
- // from an identity, it waits for the bounded handoff window and then fails.
141
- await this.ops().chooseIdentity({ name, force: false });
142
- }
143
- async listContacts() {
144
- return this.ops().listContacts();
145
- }
146
- async generateInvite(name) {
147
- return this.ops().generateInvite(name ? { name } : {});
148
- }
149
- async addContact(a) {
150
- return this.ops().addContact({ invite: a.invite, ...(a.name ? { name: a.name } : {}) });
151
- }
152
- async getMessages() {
153
- return this.ops().getMessages();
154
- }
155
- async deferMessages(msgIds) {
156
- await this.ops().deferMessages({ msg_ids: msgIds });
157
- }
158
- watchNotifications(identity, options) {
159
- return this.ops().watchNotifications(identity, options);
160
- }
161
- async listIncomingFiles() {
162
- return this.ops().listIncomingFiles();
163
- }
164
- async getFiles(wireIds) {
165
- return this.ops().getFiles({ wire_ids: wireIds });
166
- }
167
- async fetchFile(wireId) {
168
- // save_file has no SDK operation on purpose: that daemon route only reports
169
- // that a too-old connector reached it. The bytes of a retrieved file are
170
- // already on disk, so read them back and let the caller write them as this
171
- // process's own OS user.
172
- return this.ops().fetchFile(wireId);
173
- }
174
- async sendMessage(a) {
175
- const verdict = await this.ops().sendMessage({
176
- contact: a.contact, text: a.text,
177
- ...(a.replyToWireId ? { reply_to_wire_id: a.replyToWireId } : {}),
178
- });
179
- // Legacy parity: only `refused` was a tool error. `migrating`,
180
- // `deferred` and `e2e` are accepted-and-queued outcomes that it reported as
181
- // success, so they must not become failures here.
182
- if (verdict.kind === 'refused')
183
- throw new OursSendRefusedError('the daemon refused the message: the contact\'s end-to-end session must be '
184
- + 're-established after an upgrade; it was not sent and not downgraded');
185
- }
186
- async sendFile(a) {
187
- // The shared daemon may be owned by a different OS process/user and must
188
- // never be expected to open fleet-private outbox paths. SDK 2 stages bytes
189
- // read by THIS process, then sends only the opaque upload receipt.
190
- const bytes = await (this.deps.readFile?.(a.path) ?? readFile(a.path));
191
- const staged = await this.ops().uploadFile(bytes, {
192
- filename: a.filename, mime: mimeFromFilename(a.filename),
193
- });
194
- const verdict = await this.ops().sendFile({
195
- contact: a.contact, upload_id: staged.upload_id, filename: a.filename,
196
- ...(a.replyToWireId ? { reply_to_wire_id: a.replyToWireId } : {}),
197
- });
198
- // Legacy parity treated `migrating` as an error for
199
- // files and as success for messages: files are not auto-queued behind a
200
- // migration, so "queued" would be a false delivery claim.
201
- if (verdict.kind === 'refused' || verdict.kind === 'migrating')
202
- throw new OursSendRefusedError(`the daemon did not send the file (${verdict.kind}): the contact's end-to-end `
203
- + 'session must be re-established after an upgrade; files are not queued');
204
- }
205
- async close() {
206
- const client = this.client;
207
- this.client = undefined;
208
- if (!client)
209
- return;
210
- // Handing the lease back is what lets a successor bind this identity without
211
- // waiting for the supervisor to exit. A failure here is not fatal — the
212
- // daemon still reclaims the lease when this process dies.
213
- try {
214
- await client.releaseLease();
215
- }
216
- catch (error) {
217
- this.log(`lease release failed: ${error?.message ?? String(error)}`);
218
- }
219
- }
220
- ops() {
221
- if (!this.client)
222
- throw new OursDaemonError('ours daemon client is not started');
223
- return this.client;
224
- }
225
- }