@getmarrow/sdk 2.7.0 → 2.9.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.
package/dist/client.js ADDED
@@ -0,0 +1,723 @@
1
+ "use strict";
2
+ /**
3
+ * @getmarrow/sdk — MarrowClient Implementation
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MarrowClient = exports.MarrowLoopRequiredError = void 0;
7
+ const DEFAULT_HINT = 'Tip: log plans, decisions, and outcomes to Marrow so your agent improves over time.';
8
+ const POST_ORIENT_NUDGE = 'You have not logged any decisions yet this session. Before acting, call marrow_think.';
9
+ const PRE_EXIT_REMINDER = 'Before ending the session, log the outcome to Marrow so the loop closes cleanly.';
10
+ const REQUIRE_EXTERNAL_ERROR = 'Marrow require mode: log intent with marrow.think() before external actions.';
11
+ const REQUIRE_COMPLETION_ERROR = 'Marrow require mode: log the outcome with marrow.commit() before completing the session.';
12
+ function nowIso() {
13
+ return new Date().toISOString();
14
+ }
15
+ function cloneState(state) {
16
+ return {
17
+ ...state,
18
+ hints: [...state.hints],
19
+ };
20
+ }
21
+ function safeErrorMessage(error) {
22
+ return error instanceof Error ? error.message : String(error);
23
+ }
24
+ function isMeaningfulAction(meta, isExternal) {
25
+ if (meta.meaningful !== undefined)
26
+ return meta.meaningful;
27
+ if (meta.chokePoint && meta.chokePoint !== 'other')
28
+ return true;
29
+ if (meta.actionClass === 'state_changing_internal' ||
30
+ meta.actionClass === 'external_irreversible')
31
+ return true;
32
+ return isExternal;
33
+ }
34
+ class MarrowLoopRequiredError extends Error {
35
+ code = 'MARROW_LOOP_REQUIRED';
36
+ state;
37
+ constructor(message, state) {
38
+ super(message);
39
+ this.name = 'MarrowLoopRequiredError';
40
+ this.state = state;
41
+ }
42
+ }
43
+ exports.MarrowLoopRequiredError = MarrowLoopRequiredError;
44
+ class MarrowClient {
45
+ apiKey;
46
+ decisionId = null;
47
+ orientWarnings = [];
48
+ enforcement;
49
+ loopState;
50
+ sessionId;
51
+ reminderBudget;
52
+ baseUrl;
53
+ constructor(apiKey, options) {
54
+ this.apiKey = apiKey;
55
+ // Support legacy positional baseUrl: new MarrowClient(key, 'https://...')
56
+ if (typeof options === 'string') {
57
+ this.baseUrl = options;
58
+ this.sessionId = null;
59
+ }
60
+ else {
61
+ this.baseUrl = options?.baseUrl ?? 'https://api.getmarrow.ai';
62
+ this.sessionId = options?.sessionId ?? null;
63
+ }
64
+ const initialMode = (typeof options === 'object' ? options?.mode : undefined) ?? 'warn';
65
+ // Security check: warn if API key appears hardcoded
66
+ if (typeof process !== 'undefined' &&
67
+ apiKey &&
68
+ apiKey.startsWith('mrw_')) {
69
+ const fromEnv = Object.values(process.env || {}).includes(apiKey);
70
+ if (!fromEnv) {
71
+ throw new Error('[marrow] SECURITY: API key appears hardcoded in source code. Use process.env.MARROW_API_KEY instead. See: https://getmarrow.ai/docs/security');
72
+ }
73
+ }
74
+ this.enforcement = {
75
+ mode: initialMode,
76
+ remindEveryActions: 3,
77
+ externalActions: [
78
+ 'http',
79
+ 'fetch',
80
+ 'api',
81
+ 'deploy',
82
+ 'publish',
83
+ 'send',
84
+ 'email',
85
+ 'message',
86
+ 'payment',
87
+ 'write',
88
+ 'delete',
89
+ 'update',
90
+ 'create',
91
+ ],
92
+ classifyExternal: (meta) => {
93
+ if (meta.external !== undefined)
94
+ return meta.external;
95
+ const haystack = `${meta.name || ''} ${meta.action}`.toLowerCase();
96
+ return this.enforcement.externalActions.some((keyword) => haystack.includes(keyword));
97
+ },
98
+ };
99
+ this.loopState = {
100
+ mode: this.enforcement.mode,
101
+ orientedAt: null,
102
+ lastThinkAt: null,
103
+ lastOutcomeAt: null,
104
+ hasIntentLog: false,
105
+ hasOutcomeLog: false,
106
+ meaningfulActionTaken: false,
107
+ actionCountSinceLastThink: 0,
108
+ externalActionCountSinceLastThink: 0,
109
+ lastDecisionId: null,
110
+ pendingDecisionId: null,
111
+ pendingAction: null,
112
+ inFlightAction: null,
113
+ lastActionAt: null,
114
+ lastActionClass: null,
115
+ lastChokePoint: null,
116
+ recommendedNext: 'orient',
117
+ loopState: 'idle',
118
+ message: DEFAULT_HINT,
119
+ hints: [DEFAULT_HINT],
120
+ };
121
+ this.reminderBudget = {
122
+ noIntentHintShown: false,
123
+ outcomeReminderShown: false,
124
+ lastWarnedActionCount: -1,
125
+ };
126
+ }
127
+ enforce(options = {}) {
128
+ this.enforcement = {
129
+ ...this.enforcement,
130
+ ...options,
131
+ mode: options.mode || this.enforcement.mode,
132
+ remindEveryActions: options.remindEveryActions ?? this.enforcement.remindEveryActions,
133
+ externalActions: options.externalActions ?? this.enforcement.externalActions,
134
+ classifyExternal: options.classifyExternal ?? this.enforcement.classifyExternal,
135
+ };
136
+ this.loopState.mode = this.enforcement.mode;
137
+ return this.check();
138
+ }
139
+ check() {
140
+ const state = cloneState(this.loopState);
141
+ const warnings = [];
142
+ const blockReasonCodes = [];
143
+ let shouldBlock = false;
144
+ let shouldBlockCompletion = false;
145
+ let shouldBlockExternalAction = false;
146
+ if (state.mode === 'off') {
147
+ state.message = null;
148
+ state.hints = [];
149
+ return {
150
+ ok: true,
151
+ mode: state.mode,
152
+ state,
153
+ warnings: [],
154
+ recommendedNext: state.recommendedNext,
155
+ shouldBlock: false,
156
+ shouldBlockCompletion: false,
157
+ shouldBlockExternalAction: false,
158
+ blockReasonCodes: [],
159
+ };
160
+ }
161
+ if (!state.orientedAt) {
162
+ warnings.push(DEFAULT_HINT);
163
+ state.recommendedNext = 'orient';
164
+ state.loopState = 'idle';
165
+ state.message = DEFAULT_HINT;
166
+ }
167
+ else if (state.hasOutcomeLog) {
168
+ state.recommendedNext = 'done';
169
+ state.loopState = 'outcome_logged';
170
+ state.message = 'Loop closed. Ready for the next task.';
171
+ blockReasonCodes.push('loop_closed');
172
+ }
173
+ else if (!state.hasIntentLog) {
174
+ warnings.push(POST_ORIENT_NUDGE);
175
+ state.recommendedNext = 'think';
176
+ state.loopState = 'oriented';
177
+ state.message = POST_ORIENT_NUDGE;
178
+ if (state.meaningfulActionTaken) {
179
+ shouldBlockExternalAction = true;
180
+ blockReasonCodes.push('missing_intent_for_external_action');
181
+ }
182
+ }
183
+ else if (state.hasIntentLog &&
184
+ !state.hasOutcomeLog &&
185
+ state.actionCountSinceLastThink > 0) {
186
+ state.recommendedNext = 'commit';
187
+ state.loopState = 'acting';
188
+ state.message = PRE_EXIT_REMINDER;
189
+ if (state.externalActionCountSinceLastThink > 0 ||
190
+ state.meaningfulActionTaken) {
191
+ warnings.push(PRE_EXIT_REMINDER);
192
+ shouldBlockCompletion = true;
193
+ blockReasonCodes.push('missing_outcome_for_completion');
194
+ }
195
+ }
196
+ else if (state.hasIntentLog && !state.hasOutcomeLog) {
197
+ state.recommendedNext = 'act';
198
+ state.loopState = 'intent_logged';
199
+ state.message = 'Intent logged. Act, then log the outcome.';
200
+ }
201
+ else {
202
+ state.recommendedNext = state.hasOutcomeLog ? 'done' : 'act';
203
+ state.loopState = state.hasOutcomeLog ? 'outcome_logged' : 'intent_logged';
204
+ state.message = state.hasOutcomeLog
205
+ ? 'Loop closed. Ready for the next task.'
206
+ : state.message;
207
+ }
208
+ if (!state.meaningfulActionTaken && !state.hasOutcomeLog) {
209
+ blockReasonCodes.push('no_meaningful_action');
210
+ }
211
+ if (state.mode === 'require' &&
212
+ state.hasIntentLog &&
213
+ !state.hasOutcomeLog &&
214
+ state.externalActionCountSinceLastThink > 0) {
215
+ warnings.push(REQUIRE_COMPLETION_ERROR);
216
+ shouldBlock = true;
217
+ shouldBlockCompletion = true;
218
+ if (!blockReasonCodes.includes('missing_outcome_for_completion')) {
219
+ blockReasonCodes.push('missing_outcome_for_completion');
220
+ }
221
+ }
222
+ shouldBlock =
223
+ shouldBlock || shouldBlockCompletion || shouldBlockExternalAction;
224
+ return {
225
+ ok: !shouldBlock,
226
+ mode: state.mode,
227
+ state,
228
+ warnings,
229
+ recommendedNext: state.recommendedNext,
230
+ shouldBlock,
231
+ shouldBlockCompletion,
232
+ shouldBlockExternalAction,
233
+ blockReasonCodes,
234
+ };
235
+ }
236
+ async run(description, fn, options) {
237
+ if (!this.loopState.orientedAt) {
238
+ await this.orient();
239
+ }
240
+ await this.think({
241
+ action: description,
242
+ type: options?.type ?? 'general',
243
+ context: options?.context,
244
+ });
245
+ try {
246
+ const result = await fn();
247
+ await this.commit({ success: true, outcome: 'Task completed: ' + description });
248
+ return result;
249
+ }
250
+ catch (error) {
251
+ try {
252
+ await this.commit({ success: false, outcome: safeErrorMessage(error) });
253
+ }
254
+ catch {
255
+ // commit failed — don't swallow the original error
256
+ }
257
+ throw error;
258
+ }
259
+ }
260
+ async beforeAction(meta) {
261
+ const isExternal = this.enforcement.classifyExternal(meta);
262
+ const meaningful = isMeaningfulAction(meta, isExternal);
263
+ const actionTime = nowIso();
264
+ if (this.enforcement.mode === 'auto' && !this.loopState.orientedAt) {
265
+ await this.orient();
266
+ }
267
+ if (this.enforcement.mode === 'auto' && !this.loopState.hasIntentLog) {
268
+ await this.think({
269
+ action: meta.action,
270
+ type: meta.type || 'general',
271
+ context: meta.context,
272
+ });
273
+ }
274
+ if (this.enforcement.mode === 'require' &&
275
+ isExternal &&
276
+ !this.loopState.hasIntentLog) {
277
+ throw new MarrowLoopRequiredError(REQUIRE_EXTERNAL_ERROR, cloneState(this.loopState));
278
+ }
279
+ this.loopState.lastActionAt = actionTime;
280
+ this.loopState.inFlightAction = meta.action;
281
+ this.loopState.actionCountSinceLastThink += 1;
282
+ this.loopState.lastActionClass =
283
+ meta.actionClass ||
284
+ (isExternal ? 'external_irreversible' : 'low_risk_internal');
285
+ this.loopState.lastChokePoint = meta.chokePoint || 'other';
286
+ if (meaningful)
287
+ this.loopState.meaningfulActionTaken = true;
288
+ if (isExternal)
289
+ this.loopState.externalActionCountSinceLastThink += 1;
290
+ if (this.enforcement.mode === 'off') {
291
+ return this.check();
292
+ }
293
+ const check = this.check();
294
+ const shouldWarn = this.enforcement.mode === 'warn' &&
295
+ !this.loopState.hasIntentLog &&
296
+ this.loopState.actionCountSinceLastThink >=
297
+ this.enforcement.remindEveryActions &&
298
+ this.reminderBudget.lastWarnedActionCount !==
299
+ this.loopState.actionCountSinceLastThink;
300
+ if (shouldWarn) {
301
+ this.reminderBudget.lastWarnedActionCount =
302
+ this.loopState.actionCountSinceLastThink;
303
+ check.warnings.push(POST_ORIENT_NUDGE);
304
+ }
305
+ return check;
306
+ }
307
+ async afterAction(meta) {
308
+ if (this.enforcement.mode === 'auto' &&
309
+ this.loopState.pendingDecisionId &&
310
+ !meta.skipAutoOutcome) {
311
+ await this.commit({
312
+ success: meta.success ?? true,
313
+ outcome: meta.result || 'Action completed',
314
+ causedBy: meta.causedBy,
315
+ });
316
+ }
317
+ this.loopState.inFlightAction = null;
318
+ return this.check();
319
+ }
320
+ async wrap(meta, fn) {
321
+ await this.beforeAction(meta);
322
+ try {
323
+ const result = await fn();
324
+ await this.afterAction({
325
+ ...meta,
326
+ success: meta.success ?? true,
327
+ result: meta.result || 'Action completed successfully',
328
+ });
329
+ return result;
330
+ }
331
+ catch (error) {
332
+ await this.afterAction({
333
+ ...meta,
334
+ success: false,
335
+ result: meta.result || safeErrorMessage(error),
336
+ });
337
+ throw error;
338
+ }
339
+ }
340
+ async wrapPublish(action, fn, meta = {}) {
341
+ return this.wrap({
342
+ ...meta,
343
+ action,
344
+ chokePoint: 'publish',
345
+ actionClass: 'external_irreversible',
346
+ external: true,
347
+ meaningful: true,
348
+ }, fn);
349
+ }
350
+ async wrapDeploy(action, fn, meta = {}) {
351
+ return this.wrap({
352
+ ...meta,
353
+ action,
354
+ chokePoint: 'deploy',
355
+ actionClass: 'external_irreversible',
356
+ external: true,
357
+ meaningful: true,
358
+ }, fn);
359
+ }
360
+ async wrapExternalWrite(action, fn, meta = {}) {
361
+ return this.wrap({
362
+ ...meta,
363
+ action,
364
+ chokePoint: 'external_write',
365
+ actionClass: 'external_irreversible',
366
+ external: true,
367
+ meaningful: true,
368
+ }, fn);
369
+ }
370
+ async wrapHandoff(action, fn, meta = {}) {
371
+ return this.wrap({
372
+ ...meta,
373
+ action,
374
+ chokePoint: 'handoff',
375
+ actionClass: 'state_changing_internal',
376
+ external: false,
377
+ meaningful: true,
378
+ }, fn);
379
+ }
380
+ async think(params) {
381
+ const body = {
382
+ action: params.action,
383
+ type: params.type || 'general',
384
+ context: params.context,
385
+ };
386
+ if (this.decisionId) {
387
+ body.previous_decision_id = this.decisionId;
388
+ body.previous_success = params.previousSuccess ?? true;
389
+ body.previous_outcome = params.previousOutcome ?? '';
390
+ if (params.previousCausedBy)
391
+ body.previous_caused_by = params.previousCausedBy;
392
+ }
393
+ const res = await this.request('POST', '/v1/agent/think', body);
394
+ this.decisionId = res.decision_id;
395
+ const intel = (res.intelligence || {});
396
+ // Inject orient warnings into intelligence if present
397
+ if (this.orientWarnings.length > 0) {
398
+ const existingInsights = intel.insights || [];
399
+ intel.insights = [
400
+ ...this.orientWarnings.map((w) => ({
401
+ type: 'failure_pattern',
402
+ summary: w.message,
403
+ action: `Review past ${w.type} failures before proceeding`,
404
+ severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
405
+ count: 0,
406
+ })),
407
+ ...existingInsights,
408
+ ];
409
+ this.orientWarnings = [];
410
+ }
411
+ // Update loop state
412
+ this.loopState.orientedAt = this.loopState.orientedAt || nowIso();
413
+ this.loopState.lastThinkAt = nowIso();
414
+ this.loopState.hasIntentLog = true;
415
+ this.loopState.hasOutcomeLog = false;
416
+ this.loopState.actionCountSinceLastThink = 0;
417
+ this.loopState.externalActionCountSinceLastThink = 0;
418
+ this.loopState.pendingDecisionId = this.decisionId;
419
+ this.loopState.lastDecisionId = this.decisionId;
420
+ this.loopState.pendingAction = params.action;
421
+ this.loopState.recommendedNext = 'act';
422
+ this.loopState.loopState = 'intent_logged';
423
+ this.loopState.message = 'Intent logged. Act, then log the outcome.';
424
+ this.loopState.hints = [this.loopState.message];
425
+ this.reminderBudget.noIntentHintShown = true;
426
+ this.reminderBudget.outcomeReminderShown = false;
427
+ this.reminderBudget.lastWarnedActionCount = -1;
428
+ const intelligence = {
429
+ similar: intel.similar || [],
430
+ similarCount: intel.similar_count || 0,
431
+ patterns: (intel.patterns || []).map((p) => ({
432
+ patternId: (p.pattern_id || p.id || ''),
433
+ decisionType: (p.decision_type || ''),
434
+ frequency: (p.frequency || 0),
435
+ confidence: (p.confidence || 0),
436
+ })),
437
+ patternsCount: intel.patterns_count || 0,
438
+ templates: intel.templates || [],
439
+ shared: intel.shared || [],
440
+ causalChain: intel.causal_chain || null,
441
+ successRate: intel.success_rate || 0,
442
+ priorityScore: intel.priority_score || 0,
443
+ insight: intel.insight || null,
444
+ insights: intel.insights || [],
445
+ clusterId: intel.cluster_id || null,
446
+ };
447
+ const loop = this.check();
448
+ const warnings = [...loop.warnings];
449
+ const summary = [
450
+ 'Intent logged to Marrow.',
451
+ intelligence.insight
452
+ ? `Pattern hint: ${intelligence.insight}`
453
+ : intelligence.insights[0]?.summary
454
+ ? `Pattern hint: ${intelligence.insights[0].summary}`
455
+ : null,
456
+ `Recommended next step: ${loop.recommendedNext}.`,
457
+ ]
458
+ .filter(Boolean)
459
+ .join(' ');
460
+ return {
461
+ decisionId: res.decision_id,
462
+ intelligence,
463
+ streamUrl: res.stream_url,
464
+ previousCommitted: res.previous_committed,
465
+ sanitized: Boolean(res.sanitized),
466
+ upgradeHint: res.upgrade_hint
467
+ ? res.upgrade_hint
468
+ : undefined,
469
+ acceptedAs: 'intent',
470
+ warnings,
471
+ recommendedNext: loop.recommendedNext,
472
+ loop,
473
+ summary,
474
+ };
475
+ }
476
+ async commit(params) {
477
+ if (!this.decisionId) {
478
+ throw new Error('No active decision. Call think() first.');
479
+ }
480
+ const res = await this.request('POST', '/v1/agent/commit', {
481
+ decision_id: this.decisionId,
482
+ success: params.success,
483
+ outcome: params.outcome,
484
+ caused_by: params.causedBy,
485
+ });
486
+ this.decisionId = null;
487
+ this.loopState.lastOutcomeAt = nowIso();
488
+ this.loopState.hasOutcomeLog = true;
489
+ this.loopState.hasIntentLog = false;
490
+ this.loopState.pendingDecisionId = null;
491
+ this.loopState.pendingAction = null;
492
+ this.loopState.recommendedNext = 'done';
493
+ this.loopState.loopState = 'outcome_logged';
494
+ this.loopState.message = 'Loop closed. Ready for the next task.';
495
+ this.loopState.hints = [this.loopState.message];
496
+ this.reminderBudget.outcomeReminderShown = true;
497
+ const loop = this.check();
498
+ const summary = [
499
+ 'Outcome logged to Marrow.',
500
+ res.insight ? `Pattern hint: ${String(res.insight)}` : null,
501
+ 'Loop closed.',
502
+ ]
503
+ .filter(Boolean)
504
+ .join(' ');
505
+ return {
506
+ committed: res.committed,
507
+ successRate: res.success_rate,
508
+ insight: res.insight,
509
+ acceptedAs: 'outcome',
510
+ recommendedNext: loop.recommendedNext,
511
+ loop,
512
+ summary,
513
+ };
514
+ }
515
+ async orient(params) {
516
+ const patterns = await this.agentPatterns(params?.taskType ? { type: params.taskType } : undefined);
517
+ const warnings = patterns.failurePatterns
518
+ .filter((p) => p.failureRate > 0.15)
519
+ .map((p) => ({
520
+ type: p.decisionType,
521
+ failureRate: p.failureRate,
522
+ message: `${p.decisionType} has ${Math.round(p.failureRate * 100)}% failure rate over ${p.count} decisions — check lessons before proceeding`,
523
+ }));
524
+ let lessons = [];
525
+ try {
526
+ const res = await this.request('GET', `/v1/agent/think/history?type=lesson&limit=5`);
527
+ const items = (res.items || res.decisions || []);
528
+ lessons = items.map((i) => ({
529
+ summary: String(i.action || i.summary || ''),
530
+ severity: warnings.length > 0 ? 'warning' : 'info',
531
+ }));
532
+ }
533
+ catch {
534
+ // lessons endpoint optional
535
+ }
536
+ this.loopState.orientedAt = nowIso();
537
+ this.loopState.recommendedNext = this.loopState.hasIntentLog
538
+ ? 'act'
539
+ : 'think';
540
+ this.loopState.loopState = this.loopState.hasIntentLog
541
+ ? 'intent_logged'
542
+ : 'oriented';
543
+ this.loopState.message = this.loopState.hasIntentLog
544
+ ? 'Intent already logged. Proceed or log the outcome when done.'
545
+ : POST_ORIENT_NUDGE;
546
+ this.loopState.hints = [DEFAULT_HINT, this.loopState.message];
547
+ const loop = this.check();
548
+ const nudge = this.loopState.hasIntentLog ? null : POST_ORIENT_NUDGE;
549
+ const text = [
550
+ DEFAULT_HINT,
551
+ nudge,
552
+ warnings[0]?.message ? `Warning: ${warnings[0].message}` : null,
553
+ lessons[0]?.summary ? `Recent lesson: ${lessons[0].summary}` : null,
554
+ `Recommended next step: ${loop.recommendedNext}.`,
555
+ ]
556
+ .filter(Boolean)
557
+ .join(' ');
558
+ return {
559
+ warnings,
560
+ lessons,
561
+ shouldPause: warnings.some((w) => w.failureRate > 0.4),
562
+ loop,
563
+ recommendedNext: loop.recommendedNext,
564
+ nudge,
565
+ text,
566
+ };
567
+ }
568
+ async agentPatterns(params) {
569
+ const qs = new URLSearchParams();
570
+ if (params?.type)
571
+ qs.set('type', params.type);
572
+ if (params?.limit)
573
+ qs.set('limit', String(params.limit));
574
+ const res = await this.request('GET', `/v1/agent/patterns${qs.toString() ? '?' + qs.toString() : ''}`);
575
+ return {
576
+ failurePatterns: res.failure_patterns || [],
577
+ recurringDecisions: res.recurring_decisions || [],
578
+ behavioralDrift: res.behavioral_drift || {},
579
+ topFailureTypes: res.top_failure_types || [],
580
+ generatedAt: String(res.generated_at || ''),
581
+ };
582
+ }
583
+ async analytics() {
584
+ const res = await this.request('GET', '/v1/analytics');
585
+ const hs = res.health_score || {};
586
+ return {
587
+ ...res,
588
+ healthScore: {
589
+ score: Number(hs.score || 0),
590
+ label: String(hs.label || ''),
591
+ breakdown: hs.breakdown || {},
592
+ trend: String(hs.trend || ''),
593
+ vsLastWeek: String(hs.vs_last_week || ''),
594
+ },
595
+ };
596
+ }
597
+ async ask(query) {
598
+ const res = await this.request('POST', '/v1/agent/ask', { query });
599
+ return {
600
+ answer: res.answer,
601
+ stats: res.stats || null,
602
+ top_outcomes: res.top_outcomes || [],
603
+ decisions_matched: res.decisions_matched || 0,
604
+ query_keywords: res.query_keywords,
605
+ low_history: res.low_history,
606
+ };
607
+ }
608
+ async quickStatus() {
609
+ const res = await this.request('GET', '/v1/agent/status');
610
+ return {
611
+ ok: res.ok,
612
+ health: res.health || 'degraded',
613
+ message: res.message || '',
614
+ hasMemory: Boolean(res.has_memory),
615
+ lowHistory: Boolean(res.low_history),
616
+ decisionCount: res.decision_count || 0,
617
+ successRate: res.success_rate ?? null,
618
+ };
619
+ }
620
+ // Memory Control Methods
621
+ async listMemories(params) {
622
+ const qs = new URLSearchParams();
623
+ if (params?.status)
624
+ qs.set('status', params.status);
625
+ if (params?.query)
626
+ qs.set('query', params.query);
627
+ if (params?.includeDeleted)
628
+ qs.set('includeDeleted', 'true');
629
+ if (params?.limit)
630
+ qs.set('limit', String(params.limit));
631
+ if (params?.agentId)
632
+ qs.set('agent_id', params.agentId);
633
+ const res = await this.request('GET', `/v1/memories?${qs.toString()}`);
634
+ return res.data?.memories || [];
635
+ }
636
+ async getMemory(id) {
637
+ const res = await this.request('GET', `/v1/memories/${id}`);
638
+ return res.data?.memory || null;
639
+ }
640
+ async updateMemory(id, patch) {
641
+ const res = await this.request('PATCH', `/v1/memories/${id}`, patch);
642
+ return res.data.memory;
643
+ }
644
+ async deleteMemory(id, meta) {
645
+ const res = await this.request('DELETE', `/v1/memories/${id}`, meta);
646
+ return res.data.memory;
647
+ }
648
+ async markOutdated(id, meta) {
649
+ const res = await this.request('POST', `/v1/memories/${id}/outdated`, meta);
650
+ return res.data.memory;
651
+ }
652
+ async supersedeMemory(id, replacement) {
653
+ const res = await this.request('POST', `/v1/memories/${id}/supersede`, replacement);
654
+ return res.data;
655
+ }
656
+ async retrieveMemories(query, params) {
657
+ const qs = new URLSearchParams();
658
+ qs.set('q', query);
659
+ if (params?.limit)
660
+ qs.set('limit', String(params.limit));
661
+ if (params?.includeStale)
662
+ qs.set('includeStale', 'true');
663
+ if (params?.from)
664
+ qs.set('from', params.from);
665
+ if (params?.to)
666
+ qs.set('to', params.to);
667
+ if (params?.tags)
668
+ qs.set('tags', params.tags);
669
+ if (params?.source)
670
+ qs.set('source', params.source);
671
+ if (params?.status)
672
+ qs.set('status', params.status);
673
+ if (params?.shared !== undefined)
674
+ qs.set('shared', String(params.shared));
675
+ const res = await this.request('GET', `/v1/memories/retrieve?${qs.toString()}`);
676
+ return res.data;
677
+ }
678
+ async shareMemory(id, options) {
679
+ const res = await this.request('POST', `/v1/memories/${id}/share`, {
680
+ agent_ids: options.agentIds,
681
+ actor: options.actor,
682
+ });
683
+ return res.data.memory;
684
+ }
685
+ async exportMemories(options) {
686
+ const qs = new URLSearchParams();
687
+ if (options?.format)
688
+ qs.set('format', options.format);
689
+ if (options?.status)
690
+ qs.set('status', options.status);
691
+ if (options?.tags)
692
+ qs.set('tags', options.tags.join(','));
693
+ const res = await this.request('GET', `/v1/memories/export?${qs.toString()}`);
694
+ return res.data;
695
+ }
696
+ async importMemories(options) {
697
+ const res = await this.request('POST', '/v1/memories/import', options);
698
+ return res.data;
699
+ }
700
+ // Private request helper
701
+ async request(method, path, body) {
702
+ const url = `${this.baseUrl}${path}`;
703
+ const headers = {
704
+ Authorization: `Bearer ${this.apiKey}`,
705
+ 'Content-Type': 'application/json',
706
+ };
707
+ if (this.sessionId) {
708
+ headers['X-Marrow-Session-Id'] = this.sessionId;
709
+ }
710
+ const res = await fetch(url, {
711
+ method,
712
+ headers,
713
+ body: body ? JSON.stringify(body) : undefined,
714
+ });
715
+ if (!res.ok) {
716
+ const errorData = await res.json().catch(() => ({}));
717
+ throw new Error(`Marrow API error: ${res.status} ${res.statusText} — ${errorData.error || errorData.message || 'Unknown error'}`);
718
+ }
719
+ return res.json();
720
+ }
721
+ }
722
+ exports.MarrowClient = MarrowClient;
723
+ //# sourceMappingURL=client.js.map