@ours.network/fleet 0.10.3 → 0.11.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 (94) hide show
  1. package/README.md +66 -0
  2. package/dist/application/capabilities.d.ts +6 -0
  3. package/dist/application/capabilities.js +37 -0
  4. package/dist/application/errors.d.ts +31 -0
  5. package/dist/application/errors.js +51 -0
  6. package/dist/application/fleet-query-service.d.ts +42 -0
  7. package/dist/application/fleet-query-service.js +188 -0
  8. package/dist/application/log-service.d.ts +28 -0
  9. package/dist/application/log-service.js +146 -0
  10. package/dist/application/role-command-service.d.ts +37 -0
  11. package/dist/application/role-command-service.js +82 -0
  12. package/dist/application/role-creation-service.d.ts +142 -0
  13. package/dist/application/role-creation-service.js +374 -0
  14. package/dist/application/role-repository.d.ts +20 -0
  15. package/dist/application/role-repository.js +168 -0
  16. package/dist/application/session-control.d.ts +55 -0
  17. package/dist/application/session-control.js +115 -0
  18. package/dist/application/types.d.ts +156 -0
  19. package/dist/application/types.js +1 -0
  20. package/dist/cli.js +341 -3
  21. package/dist/config.d.ts +9 -2
  22. package/dist/config.js +21 -5
  23. package/dist/creation.d.ts +11 -0
  24. package/dist/creation.js +22 -5
  25. package/dist/docs.d.ts +1 -1
  26. package/dist/docs.js +56 -0
  27. package/dist/duration.d.ts +5 -0
  28. package/dist/duration.js +20 -0
  29. package/dist/index.d.ts +10 -1
  30. package/dist/index.js +9 -1
  31. package/dist/ops.d.ts +16 -0
  32. package/dist/ops.js +112 -3
  33. package/dist/paths.d.ts +1 -0
  34. package/dist/paths.js +1 -0
  35. package/dist/resolved-plan.js +7 -0
  36. package/dist/runner.js +10 -2
  37. package/dist/session/control.d.ts +4 -2
  38. package/dist/session/control.js +45 -13
  39. package/dist/spawn.d.ts +20 -2
  40. package/dist/spawn.js +94 -24
  41. package/dist/supervisor/launchd.js +17 -0
  42. package/dist/supervisor/none.js +17 -0
  43. package/dist/supervisor/systemd.js +4 -0
  44. package/dist/supervisor/types.d.ts +6 -0
  45. package/dist/tmux.d.ts +2 -0
  46. package/dist/tmux.js +8 -0
  47. package/dist/watchdog/alerts.d.ts +34 -0
  48. package/dist/watchdog/alerts.js +78 -0
  49. package/dist/watchdog/briefing.d.ts +65 -0
  50. package/dist/watchdog/briefing.js +181 -0
  51. package/dist/watchdog/config.d.ts +49 -0
  52. package/dist/watchdog/config.js +114 -0
  53. package/dist/watchdog/query.d.ts +78 -0
  54. package/dist/watchdog/query.js +124 -0
  55. package/dist/watchdog/report.d.ts +53 -0
  56. package/dist/watchdog/report.js +126 -0
  57. package/dist/watchdog/run.d.ts +61 -0
  58. package/dist/watchdog/run.js +318 -0
  59. package/dist/watchdog/scheduler.d.ts +105 -0
  60. package/dist/watchdog/scheduler.js +244 -0
  61. package/dist/watchdog/service.d.ts +46 -0
  62. package/dist/watchdog/service.js +179 -0
  63. package/dist/watchdog/store.d.ts +85 -0
  64. package/dist/watchdog/store.js +226 -0
  65. package/dist/web/audit.d.ts +22 -0
  66. package/dist/web/audit.js +54 -0
  67. package/dist/web/auth.d.ts +61 -0
  68. package/dist/web/auth.js +186 -0
  69. package/dist/web/control.d.ts +14 -0
  70. package/dist/web/control.js +110 -0
  71. package/dist/web/device-store.d.ts +27 -0
  72. package/dist/web/device-store.js +155 -0
  73. package/dist/web/events.d.ts +15 -0
  74. package/dist/web/events.js +34 -0
  75. package/dist/web/lock.d.ts +5 -0
  76. package/dist/web/lock.js +69 -0
  77. package/dist/web/runtime.d.ts +12 -0
  78. package/dist/web/runtime.js +214 -0
  79. package/dist/web/server.d.ts +37 -0
  80. package/dist/web/server.js +279 -0
  81. package/dist/web/service.d.ts +42 -0
  82. package/dist/web/service.js +180 -0
  83. package/dist/web/terminal/bridge.d.ts +27 -0
  84. package/dist/web/terminal/bridge.js +317 -0
  85. package/dist/web-app/assets/TerminalView-BvcIkuIF.js +9 -0
  86. package/dist/web-app/assets/index-B-jtLAkp.css +1 -0
  87. package/dist/web-app/assets/index-CUN7ksTw.js +9 -0
  88. package/dist/web-app/icons/ours-fleet-maskable.svg +4 -0
  89. package/dist/web-app/icons/ours-fleet.svg +4 -0
  90. package/dist/web-app/index.html +17 -0
  91. package/dist/web-app/manifest.webmanifest +15 -0
  92. package/dist/web-app/offline.html +18 -0
  93. package/dist/web-app/sw.js +51 -0
  94. package/package.json +26 -3
@@ -0,0 +1,168 @@
1
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { loadConfig, resolveMonitorConfig, resolvePermissions, ROLE_NAME_RE, } from '../config.js';
5
+ import { agentsRoot, tmpRoot } from '../paths.js';
6
+ const MAX_SNAPSHOT_BYTES = 256 * 1024;
7
+ function safeDirs(root) {
8
+ try {
9
+ return readdirSync(root, { withFileTypes: true })
10
+ .filter(entry => entry.isDirectory() && !entry.isSymbolicLink() && ROLE_NAME_RE.test(entry.name))
11
+ .map(entry => entry.name);
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ }
17
+ function view(role) {
18
+ return {
19
+ name: role.name, harness: role.harness, session: role.session, identity: role.identity,
20
+ mission: role.mission, coordinator: role.coordinator, model: role.model,
21
+ cwd: role.cwd ? redactHome(role.cwd) : undefined,
22
+ permissions: role.permissions,
23
+ };
24
+ }
25
+ function redactHome(path) {
26
+ const home = process.env.OURS_FLEET_HOME;
27
+ return home && (path === home || path.startsWith(home + '/')) ? `~${path.slice(home.length)}` : path;
28
+ }
29
+ function snapshot(path, name) {
30
+ try {
31
+ const stat = lstatSync(path);
32
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_SNAPSHOT_BYTES)
33
+ throw new Error('snapshot is not a bounded regular file');
34
+ const raw = parse(readFileSync(path, 'utf8'));
35
+ if (!raw || typeof raw !== 'object')
36
+ throw new Error('snapshot must be a mapping');
37
+ const session = raw.session === 'acp' || raw.session === 'tmux' ? raw.session : 'tmux';
38
+ return {
39
+ role: {
40
+ ...raw, name, harness: typeof raw.harness === 'string' ? raw.harness : 'claude-code',
41
+ session, identity: typeof raw.identity === 'string' ? raw.identity : name,
42
+ permissions: resolvePermissions(undefined, raw.permissions),
43
+ permissionsDeclared: raw.permissions !== undefined,
44
+ monitor: resolveMonitorConfig(undefined, raw.monitor),
45
+ sourceFile: '(temp)',
46
+ },
47
+ };
48
+ }
49
+ catch (error) {
50
+ return {
51
+ problem: {
52
+ code: 'corrupt_temp_snapshot', severity: 'error', source: 'role.yaml',
53
+ detail: `temporary snapshot is unreadable: ${error.message}`,
54
+ },
55
+ };
56
+ }
57
+ }
58
+ async function boundedMap(values, limit, fn) {
59
+ const results = new Array(values.length);
60
+ let next = 0;
61
+ await Promise.all(Array.from({ length: Math.min(limit, values.length) }, async () => {
62
+ for (;;) {
63
+ const index = next++;
64
+ if (index >= values.length)
65
+ return;
66
+ results[index] = await fn(values[index]);
67
+ }
68
+ }));
69
+ return results;
70
+ }
71
+ export class RoleRepository {
72
+ options;
73
+ constructor(options = {}) {
74
+ this.options = options;
75
+ }
76
+ async list() {
77
+ const permanentRoot = this.options.permanentRoot ?? agentsRoot();
78
+ const temporaryRoot = this.options.temporaryRoot ?? tmpRoot();
79
+ let configured = [];
80
+ let configProblem;
81
+ try {
82
+ configured = loadConfig(this.options.configPath).roles;
83
+ }
84
+ catch (error) {
85
+ configProblem = { code: 'config_invalid', severity: 'error', source: 'config', detail: error.message };
86
+ }
87
+ const permanent = new Set(safeDirs(permanentRoot));
88
+ const temporary = new Set(safeDirs(temporaryRoot));
89
+ const configuredByName = new Map(configured.map(role => [role.name, role]));
90
+ const names = [...new Set([...configuredByName.keys(), ...permanent, ...temporary])].sort();
91
+ return boundedMap(names, this.options.concurrency ?? 8, async (name) => {
92
+ const config = configuredByName.get(name);
93
+ const inPermanent = permanent.has(name);
94
+ const inTemporary = temporary.has(name);
95
+ const problems = configProblem ? [configProblem] : [];
96
+ let tempRole;
97
+ let stateHealth = inPermanent || inTemporary ? 'present' : 'missing';
98
+ if (inTemporary) {
99
+ const read = snapshot(join(temporaryRoot, name, 'role.yaml'), name);
100
+ tempRole = read.role;
101
+ if (read.problem) {
102
+ problems.push(read.problem);
103
+ stateHealth = 'corrupt';
104
+ }
105
+ }
106
+ if (inPermanent && inTemporary)
107
+ problems.push({ code: 'duplicate_state', severity: 'error', detail: 'both permanent and temporary state exist' });
108
+ const intended = config?.session ?? tempRole?.session;
109
+ const detected = await this.detect(name, intended);
110
+ if (intended && detected !== 'none' && detected !== intended && detected !== 'ambiguous')
111
+ problems.push({
112
+ code: 'backend_mismatch', severity: 'warning',
113
+ detail: `configured ${intended}, detected ${detected}`,
114
+ });
115
+ return {
116
+ id: name,
117
+ lifetime: config ? 'permanent' : inTemporary ? 'temporary' : 'orphan',
118
+ configured: Boolean(config),
119
+ config: config ? view(config) : tempRole ? view(tempRole) : undefined,
120
+ stateRef: inTemporary ? { lifetime: 'temporary' } : inPermanent ? { lifetime: 'permanent' } : undefined,
121
+ stateHealth,
122
+ configuredBackend: intended,
123
+ detectedBackend: detected,
124
+ compatibility: { compatible: true },
125
+ problems,
126
+ };
127
+ });
128
+ }
129
+ async get(id) {
130
+ if (!ROLE_NAME_RE.test(id))
131
+ return undefined;
132
+ return (await this.list()).find(role => role.id === id);
133
+ }
134
+ stateDir(record) {
135
+ if (!record.stateRef)
136
+ return undefined;
137
+ const root = record.stateRef.lifetime === 'temporary'
138
+ ? (this.options.temporaryRoot ?? tmpRoot()) : (this.options.permanentRoot ?? agentsRoot());
139
+ const candidate = join(root, record.id);
140
+ try {
141
+ const canonicalRoot = realpathSync(root);
142
+ const canonical = realpathSync(candidate);
143
+ const rel = relative(canonicalRoot, canonical);
144
+ if (rel.startsWith('..') || rel === '')
145
+ return rel === '' ? undefined : undefined;
146
+ return canonical;
147
+ }
148
+ catch {
149
+ return undefined;
150
+ }
151
+ }
152
+ async detect(name, intended) {
153
+ if (!this.options.probeBackend)
154
+ return intended ?? 'none';
155
+ const timeoutMs = this.options.timeoutMs ?? 2_000;
156
+ const result = await Promise.race([
157
+ this.options.probeBackend(name, intended),
158
+ new Promise(resolve => setTimeout(() => resolve({ acp: false, tmux: false }), timeoutMs)),
159
+ ]).catch(() => ({ acp: false, tmux: false }));
160
+ if (result.acp && result.tmux)
161
+ return 'ambiguous';
162
+ if (result.acp)
163
+ return 'acp';
164
+ if (result.tmux)
165
+ return 'tmux';
166
+ return 'none';
167
+ }
168
+ }
@@ -0,0 +1,55 @@
1
+ import { controlRequest } from '../session/control.js';
2
+ import type { SessionSnapshot } from '../session/types.js';
3
+ import { Tmux } from '../tmux.js';
4
+ import type { OutputPage, SendReceipt, SessionDescriptor } from './types.js';
5
+ export interface RoleSessionControl {
6
+ describe(): Promise<SessionDescriptor>;
7
+ snapshot(): Promise<SessionSnapshot>;
8
+ recentOutput(request?: {
9
+ since?: number;
10
+ limit?: number;
11
+ }): Promise<OutputPage>;
12
+ sendText(text: string): Promise<SendReceipt>;
13
+ interrupt?(): Promise<{
14
+ accepted: true;
15
+ }>;
16
+ respondPermission?(request: {
17
+ permissionId: string;
18
+ optionId: string;
19
+ }): Promise<{
20
+ accepted: true;
21
+ }>;
22
+ }
23
+ export declare class AcpRoleSessionAdapter implements RoleSessionControl {
24
+ private readonly stateDir;
25
+ private readonly request;
26
+ constructor(stateDir: string, request?: typeof controlRequest);
27
+ describe(): Promise<SessionDescriptor>;
28
+ snapshot(): Promise<SessionSnapshot>;
29
+ recentOutput(request?: {
30
+ since?: number;
31
+ limit?: number;
32
+ }): Promise<OutputPage>;
33
+ sendText(text: string): Promise<SendReceipt>;
34
+ interrupt(): Promise<{
35
+ accepted: true;
36
+ }>;
37
+ respondPermission(request: {
38
+ permissionId: string;
39
+ optionId: string;
40
+ }): Promise<{
41
+ accepted: true;
42
+ }>;
43
+ private call;
44
+ }
45
+ export declare class TmuxRoleSessionAdapter implements RoleSessionControl {
46
+ private readonly roleId;
47
+ private readonly tmux;
48
+ constructor(roleId: string, tmux?: Tmux);
49
+ describe(): Promise<SessionDescriptor>;
50
+ snapshot(): Promise<SessionSnapshot>;
51
+ recentOutput(request?: {
52
+ limit?: number;
53
+ }): Promise<OutputPage>;
54
+ sendText(text: string): Promise<SendReceipt>;
55
+ }
@@ -0,0 +1,115 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { controlRequest } from '../session/control.js';
3
+ import { Tmux } from '../tmux.js';
4
+ import { FleetError, normalizeError } from './errors.js';
5
+ const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
6
+ export class AcpRoleSessionAdapter {
7
+ stateDir;
8
+ request;
9
+ constructor(stateDir, request = controlRequest) {
10
+ this.stateDir = stateDir;
11
+ this.request = request;
12
+ }
13
+ async describe() {
14
+ const response = await this.call('snapshot');
15
+ const result = response;
16
+ return {
17
+ backend: 'acp', protocolVersion: result.protocolVersion ?? 1,
18
+ features: result.features ?? [], snapshot: result,
19
+ };
20
+ }
21
+ async snapshot() {
22
+ return await this.call('snapshot');
23
+ }
24
+ async recentOutput(request = {}) {
25
+ const limit = Math.min(Math.max(request.limit ?? 100, 1), 500);
26
+ const descriptor = await this.describe();
27
+ const result = descriptor.protocolVersion >= 2
28
+ ? await this.call('events_since', { since: request.since ?? 0 })
29
+ : await this.call('follow', { since: request.since ?? 0, controller: false });
30
+ const page = result;
31
+ const events = visibleEvents(page.events ?? []).slice(-limit);
32
+ return {
33
+ events, firstSeq: page.firstSeq ?? events[0]?.seq,
34
+ lastSeq: page.lastSeq ?? events.at(-1)?.seq,
35
+ truncated: Boolean(page.truncated) || (page.events?.length ?? 0) > limit,
36
+ nextCursor: events.at(-1) ? String(events.at(-1).seq) : undefined,
37
+ };
38
+ }
39
+ async sendText(text) {
40
+ if (!text.trim())
41
+ throw new FleetError('invalid_request', 'text is required');
42
+ if (Buffer.byteLength(text) > 32 * 1024)
43
+ throw new FleetError('invalid_request', 'text exceeds 32 KiB');
44
+ const result = await this.call('submit_prompt', { text });
45
+ return {
46
+ accepted: true, promptId: result.promptId, queuedBehind: result.queuedBehind,
47
+ terminalOutcomeKnown: false,
48
+ detail: result.queuedBehind
49
+ ? `accepted; ${result.queuedBehind} turn(s) queued ahead`
50
+ : 'accepted; turn may still be running',
51
+ };
52
+ }
53
+ async interrupt() {
54
+ await this.call('interrupt');
55
+ return { accepted: true };
56
+ }
57
+ async respondPermission(request) {
58
+ await this.call('respond_permission', request);
59
+ return { accepted: true };
60
+ }
61
+ async call(command, fields = {}) {
62
+ try {
63
+ const response = await this.request(this.stateDir, { command, ...fields }, 5_000);
64
+ if (!response.ok)
65
+ throw new FleetError(response.kind === 'offline' ? 'offline'
66
+ : response.kind === 'control-unavailable' ? 'control_unavailable'
67
+ : response.kind === 'timeout' ? 'timeout'
68
+ : response.kind === 'rejected' ? 'rejected' : 'backend_failure', response.error ?? `${command} failed`, { provesOffline: response.kind === 'offline' });
69
+ return response.result;
70
+ }
71
+ catch (error) {
72
+ throw normalizeError(error);
73
+ }
74
+ }
75
+ }
76
+ export class TmuxRoleSessionAdapter {
77
+ roleId;
78
+ tmux;
79
+ constructor(roleId, tmux = new Tmux()) {
80
+ this.roleId = roleId;
81
+ this.tmux = tmux;
82
+ }
83
+ async describe() {
84
+ return { backend: 'tmux', protocolVersion: 1, features: ['text', 'capture'] };
85
+ }
86
+ async snapshot() {
87
+ const alive = await this.tmux.has(this.roleId);
88
+ return { backend: 'tmux', alive, readiness: alive ? 'idle' : 'failed' };
89
+ }
90
+ async recentOutput(request = {}) {
91
+ try {
92
+ const text = await this.tmux.capture(this.roleId, Math.min(request.limit ?? 100, 500));
93
+ return { events: [], text, truncated: false };
94
+ }
95
+ catch (error) {
96
+ throw normalizeError(error);
97
+ }
98
+ }
99
+ async sendText(text) {
100
+ if (!text.trim())
101
+ throw new FleetError('invalid_request', 'text is required');
102
+ if (Buffer.byteLength(text) > 32 * 1024)
103
+ throw new FleetError('invalid_request', 'text exceeds 32 KiB');
104
+ try {
105
+ await this.tmux.sendText(this.roleId, text);
106
+ }
107
+ catch (error) {
108
+ throw normalizeError(error);
109
+ }
110
+ return {
111
+ accepted: true, promptId: randomUUID(), queuedBehind: 0, terminalOutcomeKnown: false,
112
+ detail: 'literal text and Enter sent; terminal outcome is unknown',
113
+ };
114
+ }
115
+ }
@@ -0,0 +1,156 @@
1
+ import type { CommonPermissions, SessionBackendId } from '../config.js';
2
+ import type { ExitRecord, SessionEvent, SessionSnapshot } from '../session/types.js';
3
+ export type RoleLifetime = 'permanent' | 'temporary' | 'orphan';
4
+ export type DetectedBackend = SessionBackendId | 'none' | 'ambiguous';
5
+ export interface ResolvedRoleView {
6
+ name: string;
7
+ harness: string;
8
+ session: SessionBackendId;
9
+ identity: string;
10
+ mission?: string;
11
+ coordinator?: string;
12
+ model?: string;
13
+ cwd?: string;
14
+ permissions: CommonPermissions;
15
+ }
16
+ export interface RoleRecord {
17
+ id: string;
18
+ lifetime: RoleLifetime;
19
+ configured: boolean;
20
+ config?: ResolvedRoleView;
21
+ stateRef?: {
22
+ lifetime: 'permanent' | 'temporary';
23
+ };
24
+ stateHealth: 'present' | 'missing' | 'corrupt';
25
+ configuredBackend?: SessionBackendId;
26
+ detectedBackend: DetectedBackend;
27
+ compatibility: {
28
+ compatible: boolean;
29
+ detail?: string;
30
+ };
31
+ problems: Problem[];
32
+ }
33
+ export interface Problem {
34
+ code: string;
35
+ severity: 'info' | 'warning' | 'error';
36
+ detail: string;
37
+ source?: string;
38
+ }
39
+ export interface RoleStatus {
40
+ roleId: string;
41
+ observedAt: string;
42
+ overall: 'ready' | 'busy' | 'attention' | 'offline' | 'unknown';
43
+ supervisor: {
44
+ backend: 'systemd' | 'launchd' | 'none';
45
+ liveness: 'running' | 'stopped' | 'unknown';
46
+ nativeState?: string;
47
+ detail: string;
48
+ };
49
+ session: {
50
+ backend: SessionBackendId | 'unknown';
51
+ reachability: 'online' | 'offline' | 'unavailable' | 'unknown';
52
+ readiness: 'starting' | 'idle' | 'running' | 'awaiting_permission' | 'failed' | 'unknown';
53
+ evidence: 'authoritative' | 'inferred';
54
+ sessionId?: string;
55
+ lastError?: string;
56
+ pendingPermissionId?: string;
57
+ };
58
+ restart: {
59
+ circuit: 'closed' | 'open';
60
+ consecutiveImmediateFailures: number;
61
+ nextDelayMs: number;
62
+ lastReason?: string;
63
+ openedAt?: string;
64
+ };
65
+ monitor: {
66
+ mode: 'fleet' | 'native' | 'unknown';
67
+ health: 'armed' | 'degraded' | 'failed' | 'unknown';
68
+ updatedAt?: string;
69
+ detail?: string;
70
+ stale: boolean;
71
+ };
72
+ lastExit?: ExitRecord;
73
+ isolation: {
74
+ degraded: boolean;
75
+ detail?: string;
76
+ };
77
+ problems: Problem[];
78
+ }
79
+ export interface RoleCapabilities {
80
+ protocolVersion: number;
81
+ inventory: true;
82
+ status: true;
83
+ output: {
84
+ recent: boolean;
85
+ stream: boolean;
86
+ structured: boolean;
87
+ replayCursor: boolean;
88
+ };
89
+ input: {
90
+ text: boolean;
91
+ rawKeys: boolean;
92
+ interrupt: boolean;
93
+ steering: boolean;
94
+ };
95
+ permissions: {
96
+ observe: boolean;
97
+ respond: boolean;
98
+ };
99
+ terminal: {
100
+ available: boolean;
101
+ reason?: 'wrong_backend' | 'pty_unavailable' | 'offline' | 'incompatible_runner';
102
+ multiViewer: boolean;
103
+ writerLease: boolean;
104
+ };
105
+ lifecycle: {
106
+ start: boolean;
107
+ stop: boolean;
108
+ restartResume: boolean;
109
+ restartFresh: boolean;
110
+ remove: boolean;
111
+ };
112
+ logs: {
113
+ tail: boolean;
114
+ follow: boolean;
115
+ cursor: boolean;
116
+ };
117
+ }
118
+ export interface OutputPage {
119
+ events: SessionEvent[];
120
+ text?: string;
121
+ firstSeq?: number;
122
+ lastSeq?: number;
123
+ truncated: boolean;
124
+ nextCursor?: string;
125
+ }
126
+ export interface SendReceipt {
127
+ accepted: boolean;
128
+ promptId: string;
129
+ queuedBehind: number;
130
+ terminalOutcomeKnown: boolean;
131
+ detail: string;
132
+ }
133
+ export interface SessionDescriptor {
134
+ backend: SessionBackendId;
135
+ protocolVersion: number;
136
+ features: string[];
137
+ snapshot?: SessionSnapshot;
138
+ }
139
+ export interface LogRecord {
140
+ at?: string;
141
+ text: string;
142
+ cursor?: string;
143
+ redactionApplied: boolean;
144
+ compacted?: {
145
+ kind: 'monitor_stream_hiccup';
146
+ reason: string;
147
+ count: number;
148
+ firstAt?: string;
149
+ lastAt?: string;
150
+ };
151
+ }
152
+ export interface LogPage {
153
+ records: LogRecord[];
154
+ nextCursor?: string;
155
+ truncated: boolean;
156
+ }
@@ -0,0 +1 @@
1
+ export {};