@getmarrow/sdk 2.8.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/README.md +106 -190
- package/dist/client.d.ts +143 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +723 -0
- package/dist/client.js.map +1 -0
- package/dist/factory.d.ts +18 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +31 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +7 -250
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -574
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +6 -2
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
|