@gethmy/mcp 2.23.0 → 2.25.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/cli.js +389 -141
- package/dist/index.js +356 -118
- package/dist/lib/api-client.js +137 -96
- package/dist/lib/config.js +79 -24
- package/dist/lib/oauth-refresh.js +76 -24
- package/package.json +1 -1
- package/src/api-client.ts +171 -5
- package/src/cli.ts +21 -12
- package/src/config.ts +201 -27
- package/src/playbook-metric-warnings.ts +56 -0
- package/src/prompt-builder.ts +9 -120
- package/src/read-consumer.ts +16 -0
- package/src/remote.ts +34 -6
- package/src/server.ts +195 -23
- package/src/tui/setup.ts +21 -6
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/config.ts
|
|
21
21
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
|
-
import { join as join2 } from "node:path";
|
|
23
|
+
import { dirname, join as join2, parse, resolve } from "node:path";
|
|
24
24
|
function getConfigDir() {
|
|
25
25
|
return join2(homedir(), ".harmony-mcp");
|
|
26
26
|
}
|
|
@@ -30,6 +30,22 @@ function getConfigPath() {
|
|
|
30
30
|
function getLocalConfigPath(cwd) {
|
|
31
31
|
return join2(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
32
32
|
}
|
|
33
|
+
function findLocalConfigPath(cwd) {
|
|
34
|
+
const home = resolve(homedir());
|
|
35
|
+
let dir = resolve(cwd || process.cwd());
|
|
36
|
+
const { root } = parse(dir);
|
|
37
|
+
for (;; ) {
|
|
38
|
+
if (dir !== home && dir !== root) {
|
|
39
|
+
const candidate = join2(dir, LOCAL_CONFIG_FILENAME);
|
|
40
|
+
if (existsSync2(candidate))
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
const parent = dirname(dir);
|
|
44
|
+
if (parent === dir)
|
|
45
|
+
return null;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
function emptyConfig() {
|
|
34
50
|
return {
|
|
35
51
|
apiKey: null,
|
|
@@ -81,8 +97,8 @@ function saveConfig(config) {
|
|
|
81
97
|
});
|
|
82
98
|
}
|
|
83
99
|
function loadLocalConfig(cwd) {
|
|
84
|
-
const localConfigPath =
|
|
85
|
-
if (
|
|
100
|
+
const localConfigPath = findLocalConfigPath(cwd);
|
|
101
|
+
if (localConfigPath === null) {
|
|
86
102
|
return null;
|
|
87
103
|
}
|
|
88
104
|
try {
|
|
@@ -97,7 +113,7 @@ function loadLocalConfig(cwd) {
|
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
function saveLocalConfig(config, cwd) {
|
|
100
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
116
|
+
const localConfigPath = findLocalConfigPath(cwd) ?? getLocalConfigPath(cwd);
|
|
101
117
|
const existingConfig = loadLocalConfig(cwd) || {
|
|
102
118
|
workspaceId: null,
|
|
103
119
|
projectId: null
|
|
@@ -111,7 +127,7 @@ function saveLocalConfig(config, cwd) {
|
|
|
111
127
|
writeFileSync2(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
112
128
|
}
|
|
113
129
|
function hasLocalConfig(cwd) {
|
|
114
|
-
return
|
|
130
|
+
return findLocalConfigPath(cwd) !== null;
|
|
115
131
|
}
|
|
116
132
|
function getActiveCredential() {
|
|
117
133
|
const config = loadConfig();
|
|
@@ -133,33 +149,69 @@ function getUserEmail() {
|
|
|
133
149
|
const config = loadConfig();
|
|
134
150
|
return config.userEmail;
|
|
135
151
|
}
|
|
136
|
-
function
|
|
137
|
-
if (options?.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
function setActiveContext(context, options) {
|
|
153
|
+
if (options?.global) {
|
|
154
|
+
saveConfig({
|
|
155
|
+
activeWorkspaceId: context.workspaceId,
|
|
156
|
+
activeProjectId: context.projectId
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
saveLocalConfig({ projectId }, options.cwd);
|
|
160
|
+
const localPath = findLocalConfigPath(options?.cwd);
|
|
161
|
+
if (options?.local || localPath !== null) {
|
|
162
|
+
saveLocalConfig({ workspaceId: context.workspaceId, projectId: context.projectId }, options?.cwd);
|
|
146
163
|
} else {
|
|
147
|
-
saveConfig({
|
|
164
|
+
saveConfig({
|
|
165
|
+
activeWorkspaceId: context.workspaceId,
|
|
166
|
+
activeProjectId: context.projectId
|
|
167
|
+
});
|
|
148
168
|
}
|
|
149
169
|
}
|
|
150
|
-
function
|
|
170
|
+
function setActiveWorkspace(workspaceId, options) {
|
|
171
|
+
const currentWorkspaceId = getActiveWorkspaceId(options?.cwd);
|
|
172
|
+
const keepProject = currentWorkspaceId === workspaceId;
|
|
173
|
+
setActiveContext({
|
|
174
|
+
workspaceId,
|
|
175
|
+
projectId: keepProject ? getActiveProjectId(options?.cwd) : null
|
|
176
|
+
}, options);
|
|
177
|
+
}
|
|
178
|
+
function readActiveContext(cwd) {
|
|
151
179
|
const localConfig = loadLocalConfig(cwd);
|
|
152
|
-
if (localConfig
|
|
153
|
-
return
|
|
180
|
+
if (localConfig) {
|
|
181
|
+
return {
|
|
182
|
+
workspaceId: localConfig.workspaceId ?? null,
|
|
183
|
+
projectId: localConfig.projectId ?? null
|
|
184
|
+
};
|
|
154
185
|
}
|
|
155
|
-
|
|
186
|
+
const globalConfig = loadConfig();
|
|
187
|
+
return {
|
|
188
|
+
workspaceId: globalConfig.activeWorkspaceId,
|
|
189
|
+
projectId: globalConfig.activeProjectId
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getActiveWorkspaceId(cwd) {
|
|
193
|
+
return readActiveContext(cwd).workspaceId;
|
|
156
194
|
}
|
|
157
195
|
function getActiveProjectId(cwd) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
196
|
+
return readActiveContext(cwd).projectId;
|
|
197
|
+
}
|
|
198
|
+
function getActiveContext(cwd) {
|
|
199
|
+
return describeActiveContext({
|
|
200
|
+
projectId: getActiveProjectId(cwd),
|
|
201
|
+
workspaceId: getActiveWorkspaceId(cwd)
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function describeActiveContext(context) {
|
|
205
|
+
const { projectId, workspaceId } = context;
|
|
206
|
+
if (projectId && !workspaceId) {
|
|
207
|
+
return {
|
|
208
|
+
projectId,
|
|
209
|
+
workspaceId,
|
|
210
|
+
consistent: false,
|
|
211
|
+
note: `An active project (${projectId}) is set with no active workspace, so ` + "workspace-scoped tools cannot resolve one from it. Re-set it with " + "harmony_set_project_context, or pass workspaceId explicitly."
|
|
212
|
+
};
|
|
161
213
|
}
|
|
162
|
-
return
|
|
214
|
+
return { projectId, workspaceId, consistent: true, note: null };
|
|
163
215
|
}
|
|
164
216
|
function isConfigured() {
|
|
165
217
|
const config = loadConfig();
|
|
@@ -302,15 +354,7 @@ ${lines.join(`
|
|
|
302
354
|
`)}`;
|
|
303
355
|
}
|
|
304
356
|
function generatePrompt(options) {
|
|
305
|
-
const {
|
|
306
|
-
card,
|
|
307
|
-
column,
|
|
308
|
-
variant,
|
|
309
|
-
customConstraints,
|
|
310
|
-
memories,
|
|
311
|
-
assembledContext,
|
|
312
|
-
assemblyId
|
|
313
|
-
} = options;
|
|
357
|
+
const { card, column, variant, customConstraints, memories, assemblyId } = options;
|
|
314
358
|
const contextOpts = {
|
|
315
359
|
includeTitle: true,
|
|
316
360
|
includeDescription: true,
|
|
@@ -388,10 +432,7 @@ ${card.description}`);
|
|
|
388
432
|
roleFraming.outputSuggestions.forEach((s) => {
|
|
389
433
|
sections.push(`- ${s}`);
|
|
390
434
|
});
|
|
391
|
-
if (
|
|
392
|
-
sections.push(`
|
|
393
|
-
${assembledContext}`);
|
|
394
|
-
} else if (memories && memories.length > 0) {
|
|
435
|
+
if (memories && memories.length > 0) {
|
|
395
436
|
sections.push(`
|
|
396
437
|
## Relevant Memories`);
|
|
397
438
|
sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
|
|
@@ -402,7 +443,7 @@ ${assembledContext}`);
|
|
|
402
443
|
sections.push(memory.content);
|
|
403
444
|
}
|
|
404
445
|
}
|
|
405
|
-
const oneThingLine = synthesizeOneThing(card, subtasks, links
|
|
446
|
+
const oneThingLine = synthesizeOneThing(card, subtasks, links);
|
|
406
447
|
if (oneThingLine) {
|
|
407
448
|
sections.push(`
|
|
408
449
|
## Recommended Next Step
|
|
@@ -429,7 +470,7 @@ ${customConstraints}`);
|
|
|
429
470
|
*Card #${card.short_id} | Generated for ${variant} mode*`);
|
|
430
471
|
const prompt = sections.join(`
|
|
431
472
|
`);
|
|
432
|
-
const memoryCount =
|
|
473
|
+
const memoryCount = memories?.length ?? 0;
|
|
433
474
|
return {
|
|
434
475
|
prompt,
|
|
435
476
|
variant,
|
|
@@ -450,40 +491,7 @@ ${customConstraints}`);
|
|
|
450
491
|
version: PROMPT_TEMPLATE_VERSION
|
|
451
492
|
};
|
|
452
493
|
}
|
|
453
|
-
function
|
|
454
|
-
const result = {
|
|
455
|
-
lastSessionStatus: null,
|
|
456
|
-
lastSessionTask: null,
|
|
457
|
-
lastSessionProgress: null,
|
|
458
|
-
blockers: [],
|
|
459
|
-
procedureNextStep: null
|
|
460
|
-
};
|
|
461
|
-
const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
|
|
462
|
-
if (sessionMatches && sessionMatches.length > 0) {
|
|
463
|
-
const latest = sessionMatches[0];
|
|
464
|
-
if (/Completed work on/i.test(latest)) {
|
|
465
|
-
result.lastSessionStatus = "completed";
|
|
466
|
-
} else if (/Paused work on|status:\s*paused/i.test(latest)) {
|
|
467
|
-
result.lastSessionStatus = "paused";
|
|
468
|
-
}
|
|
469
|
-
const taskMatch = latest.match(/Final task:\s*(.+)/);
|
|
470
|
-
if (taskMatch)
|
|
471
|
-
result.lastSessionTask = taskMatch[1].trim();
|
|
472
|
-
const progressMatch = latest.match(/Progress:\s*(\d+)%/);
|
|
473
|
-
if (progressMatch)
|
|
474
|
-
result.lastSessionProgress = parseInt(progressMatch[1], 10);
|
|
475
|
-
}
|
|
476
|
-
const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
|
|
477
|
-
if (blockerMatches) {
|
|
478
|
-
result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
|
|
479
|
-
}
|
|
480
|
-
const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
|
|
481
|
-
if (stepMatches && stepMatches.length > 0) {
|
|
482
|
-
result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
|
|
483
|
-
}
|
|
484
|
-
return result;
|
|
485
|
-
}
|
|
486
|
-
function synthesizeOneThing(card, subtasks, links, assembledContext) {
|
|
494
|
+
function synthesizeOneThing(card, subtasks, links) {
|
|
487
495
|
if (card.done)
|
|
488
496
|
return null;
|
|
489
497
|
const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
|
|
@@ -491,14 +499,6 @@ function synthesizeOneThing(card, subtasks, links, assembledContext) {
|
|
|
491
499
|
const blocker = blockers[0];
|
|
492
500
|
return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
|
|
493
501
|
}
|
|
494
|
-
const session = assembledContext ? extractSessionInsights(assembledContext) : null;
|
|
495
|
-
if (session?.blockers && session.blockers.length > 0) {
|
|
496
|
-
return `Resolve blocker: ${session.blockers[0]}`;
|
|
497
|
-
}
|
|
498
|
-
if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
|
|
499
|
-
const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
|
|
500
|
-
return `Resume previous session${progress}: "${session.lastSessionTask}".`;
|
|
501
|
-
}
|
|
502
502
|
if (subtasks.length > 0) {
|
|
503
503
|
const completed = subtasks.filter((s) => s.completed).length;
|
|
504
504
|
if (completed === subtasks.length) {
|
|
@@ -509,12 +509,6 @@ function synthesizeOneThing(card, subtasks, links, assembledContext) {
|
|
|
509
509
|
return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
|
-
if (session?.procedureNextStep) {
|
|
513
|
-
return `Follow procedure: ${session.procedureNextStep}`;
|
|
514
|
-
}
|
|
515
|
-
if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
|
|
516
|
-
return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
|
|
517
|
-
}
|
|
518
512
|
if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
|
|
519
513
|
return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
|
|
520
514
|
}
|
|
@@ -878,7 +872,7 @@ function lockPath() {
|
|
|
878
872
|
return join3(getConfigDir(), LOCK_FILENAME);
|
|
879
873
|
}
|
|
880
874
|
function sleep(ms) {
|
|
881
|
-
return new Promise((
|
|
875
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
882
876
|
}
|
|
883
877
|
async function withRefreshLock(fn) {
|
|
884
878
|
const path = lockPath();
|
|
@@ -1381,6 +1375,9 @@ import {
|
|
|
1381
1375
|
ReadResourceRequestSchema
|
|
1382
1376
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1383
1377
|
import { z } from "zod";
|
|
1378
|
+
|
|
1379
|
+
// src/api-client.ts
|
|
1380
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1384
1381
|
// ../harmony-shared/dist/agentStaleness.js
|
|
1385
1382
|
var AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
|
|
1386
1383
|
var AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
|
|
@@ -1502,12 +1499,97 @@ var TIMINGS = {
|
|
|
1502
1499
|
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
1503
1500
|
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
1504
1501
|
};
|
|
1502
|
+
// ../harmony-shared/dist/declaredGateMetrics.js
|
|
1503
|
+
function declaredGateMetricsFromAgents(agents) {
|
|
1504
|
+
const names = new Set;
|
|
1505
|
+
let known = false;
|
|
1506
|
+
for (const agent of agents) {
|
|
1507
|
+
const declared = agent.declared_gate_metrics;
|
|
1508
|
+
if (!Array.isArray(declared))
|
|
1509
|
+
continue;
|
|
1510
|
+
known = true;
|
|
1511
|
+
for (const name of declared) {
|
|
1512
|
+
if (typeof name === "string" && name.trim())
|
|
1513
|
+
names.add(name.trim());
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return { names, known };
|
|
1517
|
+
}
|
|
1518
|
+
// ../harmony-shared/dist/gateConfigError.js
|
|
1519
|
+
var GATE_CONFIG_ERROR_KEY = "configError";
|
|
1520
|
+
var GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
|
|
1505
1521
|
// ../harmony-shared/dist/playbookStage.js
|
|
1522
|
+
var DEFAULT_LOOP_MAX_ITERATIONS = 5;
|
|
1523
|
+
function normalizeLoopDef(raw) {
|
|
1524
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
1525
|
+
return null;
|
|
1526
|
+
const obj = raw;
|
|
1527
|
+
if (obj.mode !== "converge" && obj.mode !== "fanout")
|
|
1528
|
+
return null;
|
|
1529
|
+
const mode = obj.mode;
|
|
1530
|
+
const rawMax = obj.max_iterations;
|
|
1531
|
+
const maxInt = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 1 ? Math.floor(rawMax) : DEFAULT_LOOP_MAX_ITERATIONS;
|
|
1532
|
+
const exitGate = obj.exit_gate && typeof obj.exit_gate === "object" && !Array.isArray(obj.exit_gate) ? obj.exit_gate : null;
|
|
1533
|
+
const def = { mode, max_iterations: maxInt };
|
|
1534
|
+
if (exitGate)
|
|
1535
|
+
def.exit_gate = exitGate;
|
|
1536
|
+
if (obj.item_source && typeof obj.item_source === "object" && !Array.isArray(obj.item_source)) {
|
|
1537
|
+
def.item_source = obj.item_source;
|
|
1538
|
+
}
|
|
1539
|
+
if (typeof obj.concurrency === "number" && obj.concurrency >= 1) {
|
|
1540
|
+
def.concurrency = Math.floor(obj.concurrency);
|
|
1541
|
+
}
|
|
1542
|
+
if (obj.on_item_fail === "continue" || obj.on_item_fail === "halt") {
|
|
1543
|
+
def.on_item_fail = obj.on_item_fail;
|
|
1544
|
+
}
|
|
1545
|
+
return def;
|
|
1546
|
+
}
|
|
1547
|
+
function readStageDefs(def) {
|
|
1548
|
+
if (def.steps_version !== 2)
|
|
1549
|
+
return [];
|
|
1550
|
+
return Array.isArray(def.steps) ? def.steps : [];
|
|
1551
|
+
}
|
|
1506
1552
|
var STAGE_DAEMON_OWNED_TOOLS = [
|
|
1507
1553
|
"mcp__harmony__harmony_end_agent_session",
|
|
1508
1554
|
"mcp__harmony__harmony_start_agent_session",
|
|
1509
1555
|
"mcp__harmony__harmony_move_card"
|
|
1510
1556
|
];
|
|
1557
|
+
function customGateMetric(gate) {
|
|
1558
|
+
if (gate === null || typeof gate !== "object" || Array.isArray(gate)) {
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1561
|
+
const record = gate;
|
|
1562
|
+
if (record.kind !== "custom")
|
|
1563
|
+
return null;
|
|
1564
|
+
if (record.pendingEngine === true)
|
|
1565
|
+
return null;
|
|
1566
|
+
const metric = typeof record.metric === "string" ? record.metric.trim() : "";
|
|
1567
|
+
return metric ? metric : null;
|
|
1568
|
+
}
|
|
1569
|
+
function referencedGateMetrics(def) {
|
|
1570
|
+
const out = [];
|
|
1571
|
+
for (const stage of readStageDefs(def)) {
|
|
1572
|
+
if (!stage || typeof stage !== "object")
|
|
1573
|
+
continue;
|
|
1574
|
+
const stageId = typeof stage.id === "string" ? stage.id : "";
|
|
1575
|
+
const stageName = typeof stage.name === "string" ? stage.name : stageId;
|
|
1576
|
+
const gateMetric = customGateMetric(stage.gate);
|
|
1577
|
+
if (gateMetric) {
|
|
1578
|
+
out.push({ stageId, stageName, metric: gateMetric, source: "gate" });
|
|
1579
|
+
}
|
|
1580
|
+
const loop = normalizeLoopDef(stage.loop);
|
|
1581
|
+
const loopMetric = loop?.exit_gate ? customGateMetric(loop.exit_gate) : null;
|
|
1582
|
+
if (loopMetric) {
|
|
1583
|
+
out.push({
|
|
1584
|
+
stageId,
|
|
1585
|
+
stageName,
|
|
1586
|
+
metric: loopMetric,
|
|
1587
|
+
source: "loop_exit_gate"
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return out;
|
|
1592
|
+
}
|
|
1511
1593
|
// ../harmony-shared/dist/reviewTools.js
|
|
1512
1594
|
var REVIEW_DISALLOWED_TOOLS = [
|
|
1513
1595
|
...STAGE_DAEMON_OWNED_TOOLS,
|
|
@@ -1541,7 +1623,20 @@ function getRetryDelay(attempt) {
|
|
|
1541
1623
|
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1542
1624
|
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1543
1625
|
}
|
|
1544
|
-
var sleep2 = (ms) => new Promise((
|
|
1626
|
+
var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1627
|
+
function buildMemoryQuery(title, description) {
|
|
1628
|
+
const DESCRIPTION_CAP = 600;
|
|
1629
|
+
const trimmedTitle = title.trim();
|
|
1630
|
+
const trimmedBody = (description ?? "").trim();
|
|
1631
|
+
if (!trimmedTitle && !trimmedBody)
|
|
1632
|
+
return "";
|
|
1633
|
+
if (!trimmedTitle)
|
|
1634
|
+
return trimmedBody.slice(0, DESCRIPTION_CAP);
|
|
1635
|
+
if (!trimmedBody)
|
|
1636
|
+
return trimmedTitle;
|
|
1637
|
+
return `${trimmedTitle}
|
|
1638
|
+
${trimmedBody.slice(0, DESCRIPTION_CAP)}`;
|
|
1639
|
+
}
|
|
1545
1640
|
|
|
1546
1641
|
class Semaphore {
|
|
1547
1642
|
permits;
|
|
@@ -1554,7 +1649,7 @@ class Semaphore {
|
|
|
1554
1649
|
this.permits--;
|
|
1555
1650
|
return;
|
|
1556
1651
|
}
|
|
1557
|
-
return new Promise((
|
|
1652
|
+
return new Promise((resolve2) => this.queue.push(resolve2));
|
|
1558
1653
|
}
|
|
1559
1654
|
release() {
|
|
1560
1655
|
const next = this.queue.shift();
|
|
@@ -2012,6 +2107,14 @@ class HarmonyApiClient {
|
|
|
2012
2107
|
params.set("sinceSeq", String(sinceSeq));
|
|
2013
2108
|
return this.request("GET", `/cards/${cardId}/agent-messages?${params.toString()}`);
|
|
2014
2109
|
}
|
|
2110
|
+
async postBudgetDecision(cardId, data) {
|
|
2111
|
+
return this.request("POST", `/cards/${cardId}/budget-decisions`, data);
|
|
2112
|
+
}
|
|
2113
|
+
async getBudgetDecisions(cardId, sinceIso) {
|
|
2114
|
+
const params = new URLSearchParams;
|
|
2115
|
+
params.set("sinceIso", sinceIso);
|
|
2116
|
+
return this.request("GET", `/cards/${cardId}/budget-decisions?${params.toString()}`);
|
|
2117
|
+
}
|
|
2015
2118
|
async updateAgentProgress(cardId, data) {
|
|
2016
2119
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
2017
2120
|
}
|
|
@@ -2057,6 +2160,8 @@ class HarmonyApiClient {
|
|
|
2057
2160
|
params.set("offset", String(options.offset));
|
|
2058
2161
|
if (options.include_superseded)
|
|
2059
2162
|
params.set("include_superseded", "true");
|
|
2163
|
+
if (options.consumer)
|
|
2164
|
+
params.set("consumer", options.consumer);
|
|
2060
2165
|
if (options.include_episodes)
|
|
2061
2166
|
params.set("include_episodes", "true");
|
|
2062
2167
|
return this.request("GET", `/memory/entities?${params.toString()}`);
|
|
@@ -2112,6 +2217,12 @@ class HarmonyApiClient {
|
|
|
2112
2217
|
if (options.topK !== undefined) {
|
|
2113
2218
|
entities = entities.slice(0, options.topK);
|
|
2114
2219
|
}
|
|
2220
|
+
if (options.consumer) {
|
|
2221
|
+
const deliveredIds = entities.map((e) => e.id).filter((id) => typeof id === "string");
|
|
2222
|
+
if (deliveredIds.length > 0) {
|
|
2223
|
+
this.batchTouchMemoryEntities(deliveredIds, options.consumer).catch(() => {});
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2115
2226
|
return { entities };
|
|
2116
2227
|
}
|
|
2117
2228
|
async deleteMemoryEntity(entityId) {
|
|
@@ -2120,9 +2231,10 @@ class HarmonyApiClient {
|
|
|
2120
2231
|
async touchMemoryEntity(entityId) {
|
|
2121
2232
|
return this.request("POST", `/memory/entities/${entityId}/touch`);
|
|
2122
2233
|
}
|
|
2123
|
-
async batchTouchMemoryEntities(entityIds) {
|
|
2234
|
+
async batchTouchMemoryEntities(entityIds, consumer) {
|
|
2124
2235
|
return this.request("POST", "/memory/entities/batch-touch", {
|
|
2125
|
-
entity_ids: entityIds
|
|
2236
|
+
entity_ids: entityIds,
|
|
2237
|
+
...consumer ? { consumer } : {}
|
|
2126
2238
|
});
|
|
2127
2239
|
}
|
|
2128
2240
|
async createMemoryRelation(data) {
|
|
@@ -2148,8 +2260,12 @@ class HarmonyApiClient {
|
|
|
2148
2260
|
params.append("tags", tag);
|
|
2149
2261
|
if (options?.include_superseded)
|
|
2150
2262
|
params.set("include_superseded", "true");
|
|
2263
|
+
if (options?.consumer)
|
|
2264
|
+
params.set("consumer", options.consumer);
|
|
2151
2265
|
if (options?.include_episodes)
|
|
2152
2266
|
params.set("include_episodes", "true");
|
|
2267
|
+
if (options?.assembly_id)
|
|
2268
|
+
params.set("assembly_id", options.assembly_id);
|
|
2153
2269
|
return this.request("GET", `/memory/search?${params.toString()}`);
|
|
2154
2270
|
}
|
|
2155
2271
|
async getVaultIndex(options) {
|
|
@@ -2161,6 +2277,8 @@ class HarmonyApiClient {
|
|
|
2161
2277
|
params.set("type", options.type);
|
|
2162
2278
|
if (options.limit !== undefined)
|
|
2163
2279
|
params.set("limit", String(options.limit));
|
|
2280
|
+
if (options.consumer)
|
|
2281
|
+
params.set("consumer", options.consumer);
|
|
2164
2282
|
if (options.include_episodes)
|
|
2165
2283
|
params.set("include_episodes", "true");
|
|
2166
2284
|
return this.request("GET", `/memory/index?${params.toString()}`);
|
|
@@ -2174,6 +2292,8 @@ class HarmonyApiClient {
|
|
|
2174
2292
|
params.set("type", options.type);
|
|
2175
2293
|
if (options.limit !== undefined)
|
|
2176
2294
|
params.set("limit", String(options.limit));
|
|
2295
|
+
if (options.consumer)
|
|
2296
|
+
params.set("consumer", options.consumer);
|
|
2177
2297
|
if (options.include_episodes)
|
|
2178
2298
|
params.set("include_episodes", "true");
|
|
2179
2299
|
return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
|
|
@@ -2233,6 +2353,8 @@ class HarmonyApiClient {
|
|
|
2233
2353
|
params.set("type", options.type);
|
|
2234
2354
|
if (options?.limit !== undefined)
|
|
2235
2355
|
params.set("limit", String(options.limit));
|
|
2356
|
+
if (options?.consumer)
|
|
2357
|
+
params.set("consumer", options.consumer);
|
|
2236
2358
|
if (options?.include_episodes)
|
|
2237
2359
|
params.set("include_episodes", "true");
|
|
2238
2360
|
return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
|
|
@@ -2338,14 +2460,15 @@ class HarmonyApiClient {
|
|
|
2338
2460
|
} catch {}
|
|
2339
2461
|
}
|
|
2340
2462
|
const variant = options.variant || "execute";
|
|
2341
|
-
const
|
|
2342
|
-
const assemblyId = undefined;
|
|
2463
|
+
const assemblyId = randomUUID2();
|
|
2343
2464
|
let memories;
|
|
2344
2465
|
try {
|
|
2345
2466
|
if (options.workspaceId && cardData.title) {
|
|
2346
|
-
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
2467
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, buildMemoryQuery(cardData.title, cardData.description), {
|
|
2347
2468
|
project_id: options.projectId,
|
|
2348
|
-
limit: 5
|
|
2469
|
+
limit: 5,
|
|
2470
|
+
consumer: "agent-prompt",
|
|
2471
|
+
assembly_id: assemblyId
|
|
2349
2472
|
});
|
|
2350
2473
|
if (memoryResult.entities?.length > 0) {
|
|
2351
2474
|
memories = memoryResult.entities.map((e) => ({
|
|
@@ -2369,7 +2492,6 @@ class HarmonyApiClient {
|
|
|
2369
2492
|
contextOptions: options.contextOptions,
|
|
2370
2493
|
customConstraints: options.customConstraints,
|
|
2371
2494
|
memories,
|
|
2372
|
-
assembledContext: assembledContextStr,
|
|
2373
2495
|
assemblyId
|
|
2374
2496
|
});
|
|
2375
2497
|
try {
|
|
@@ -2700,7 +2822,7 @@ async function autoExpandGraph(client3, entityId, title, content, _tags, workspa
|
|
|
2700
2822
|
});
|
|
2701
2823
|
candidates = entities.filter((e) => e.id !== entityId && (e.confidence ?? 1) >= 0.4).slice(0, maxRelations);
|
|
2702
2824
|
if (candidates.length === 0) {
|
|
2703
|
-
await new Promise((
|
|
2825
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
2704
2826
|
const retry = await client3.searchMemoryEntities(workspaceId, query, {
|
|
2705
2827
|
project_id: projectId,
|
|
2706
2828
|
limit: 20
|
|
@@ -3283,6 +3405,34 @@ async function onboardNewUser(params) {
|
|
|
3283
3405
|
};
|
|
3284
3406
|
}
|
|
3285
3407
|
|
|
3408
|
+
// src/playbook-metric-warnings.ts
|
|
3409
|
+
function playbookMetricWarnings(agents, steps) {
|
|
3410
|
+
if (!Array.isArray(steps))
|
|
3411
|
+
return [];
|
|
3412
|
+
const declared = declaredGateMetricsFromAgents(agents);
|
|
3413
|
+
if (!declared.known)
|
|
3414
|
+
return [];
|
|
3415
|
+
const warnings = [];
|
|
3416
|
+
const seen = new Set;
|
|
3417
|
+
for (const ref of referencedGateMetrics({ steps, steps_version: 2 })) {
|
|
3418
|
+
if (declared.names.has(ref.metric) || seen.has(ref.metric))
|
|
3419
|
+
continue;
|
|
3420
|
+
seen.add(ref.metric);
|
|
3421
|
+
warnings.push(`Gate metric "${ref.metric}" (stage "${ref.stageName}") is not declared by any agent in this workspace — a stage run gating on it will hold until a daemon declares it under agent.playbooks.metrics.${ref.metric}.`);
|
|
3422
|
+
}
|
|
3423
|
+
return warnings;
|
|
3424
|
+
}
|
|
3425
|
+
async function collectPlaybookMetricWarnings(client3, workspaceId, steps) {
|
|
3426
|
+
if (!workspaceId || !Array.isArray(steps))
|
|
3427
|
+
return [];
|
|
3428
|
+
try {
|
|
3429
|
+
const { agents } = await client3.listWorkspaceAgents(workspaceId);
|
|
3430
|
+
return playbookMetricWarnings(agents, steps);
|
|
3431
|
+
} catch {
|
|
3432
|
+
return [];
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3286
3436
|
// src/skills.ts
|
|
3287
3437
|
import {
|
|
3288
3438
|
existsSync as existsSync4,
|
|
@@ -3292,7 +3442,7 @@ import {
|
|
|
3292
3442
|
writeFileSync as writeFileSync3
|
|
3293
3443
|
} from "node:fs";
|
|
3294
3444
|
import { homedir as homedir3 } from "node:os";
|
|
3295
|
-
import { dirname, join as join5 } from "node:path";
|
|
3445
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
3296
3446
|
init_config();
|
|
3297
3447
|
|
|
3298
3448
|
// src/hmy-config.ts
|
|
@@ -3439,7 +3589,7 @@ function stripSkillPreamble(content) {
|
|
|
3439
3589
|
`;
|
|
3440
3590
|
}
|
|
3441
3591
|
function atomicWrite(filePath, content) {
|
|
3442
|
-
const dir =
|
|
3592
|
+
const dir = dirname2(filePath);
|
|
3443
3593
|
if (!existsSync4(dir))
|
|
3444
3594
|
mkdirSync3(dir, { recursive: true });
|
|
3445
3595
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -3530,10 +3680,10 @@ async function refreshSkills(opts = {}) {
|
|
|
3530
3680
|
continue;
|
|
3531
3681
|
let siblingPath;
|
|
3532
3682
|
if (samplePath.endsWith("SKILL.md")) {
|
|
3533
|
-
const parentDir =
|
|
3683
|
+
const parentDir = dirname2(dirname2(samplePath));
|
|
3534
3684
|
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
3535
3685
|
} else {
|
|
3536
|
-
const parentDir =
|
|
3686
|
+
const parentDir = dirname2(samplePath);
|
|
3537
3687
|
siblingPath = `${parentDir}/${name}.md`;
|
|
3538
3688
|
}
|
|
3539
3689
|
if (existsSync4(siblingPath)) {
|
|
@@ -4327,7 +4477,7 @@ var TOOLS = {
|
|
|
4327
4477
|
}
|
|
4328
4478
|
},
|
|
4329
4479
|
harmony_classify_card: {
|
|
4330
|
-
description: "
|
|
4480
|
+
description: "DEPRECATED — run sizing now happens at daemon pickup and is run-scoped, so nothing reads `model_tier`, `intent` or `complexity_score` any more; this tool still writes them, but only the type label has an effect. Prefer letting card creation apply the type label. Sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Idempotent; never touches the user-owned `model_override`.",
|
|
4331
4481
|
inputSchema: {
|
|
4332
4482
|
type: "object",
|
|
4333
4483
|
properties: {
|
|
@@ -4581,11 +4731,15 @@ var TOOLS = {
|
|
|
4581
4731
|
}
|
|
4582
4732
|
},
|
|
4583
4733
|
harmony_set_project_context: {
|
|
4584
|
-
description: "Set the active project context for subsequent operations",
|
|
4734
|
+
description: "Set the active project context for subsequent operations. The project's workspace is set with it, so the two can never point at different places; pass workspaceId to skip the lookup.",
|
|
4585
4735
|
inputSchema: {
|
|
4586
4736
|
type: "object",
|
|
4587
4737
|
properties: {
|
|
4588
|
-
projectId: { type: "string" }
|
|
4738
|
+
projectId: { type: "string" },
|
|
4739
|
+
workspaceId: {
|
|
4740
|
+
type: "string",
|
|
4741
|
+
description: "The workspace this project belongs to. Optional — looked up when omitted."
|
|
4742
|
+
}
|
|
4589
4743
|
},
|
|
4590
4744
|
required: ["projectId"]
|
|
4591
4745
|
}
|
|
@@ -5389,13 +5543,26 @@ var TOOLS = {
|
|
|
5389
5543
|
type: "array",
|
|
5390
5544
|
description: "The playbook's ordered stage objects.",
|
|
5391
5545
|
items: { type: "object" }
|
|
5546
|
+
},
|
|
5547
|
+
triggerType: {
|
|
5548
|
+
type: "string",
|
|
5549
|
+
enum: ["manual", "auto"],
|
|
5550
|
+
description: "'manual' (default) — the playbook is applied by a person. 'auto' — it claims matching cards itself, and requires autoBind."
|
|
5551
|
+
},
|
|
5552
|
+
autoBind: {
|
|
5553
|
+
type: "object",
|
|
5554
|
+
description: "Auto-bind rule: {priority?: number, mode?: 'all'|'any', when: [{path, op, value}]}. Conditions are evaluated against the card's labels (lowercased), intent, complexity_score and priority with the gate operators eq/neq/gte/gt/lte/lt/contains/exists; 'contains' on labels is membership. Stored even while triggerType is 'manual', so a rule can be armed later without re-authoring it."
|
|
5555
|
+
},
|
|
5556
|
+
catalogId: {
|
|
5557
|
+
type: "string",
|
|
5558
|
+
description: "Slug of the built-in template this came from (provenance only; never used to match)."
|
|
5392
5559
|
}
|
|
5393
5560
|
},
|
|
5394
5561
|
required: ["name"]
|
|
5395
5562
|
}
|
|
5396
5563
|
},
|
|
5397
5564
|
harmony_update_playbook: {
|
|
5398
|
-
description: "Update a playbook's name, description, steps/stages, enabled flag,
|
|
5565
|
+
description: "Update a playbook's name, description, steps/stages, enabled flag, lifecycle state ('active'|'deprecated'), or its auto-bind rule and arming.",
|
|
5399
5566
|
inputSchema: {
|
|
5400
5567
|
type: "object",
|
|
5401
5568
|
properties: {
|
|
@@ -5418,6 +5585,15 @@ var TOOLS = {
|
|
|
5418
5585
|
type: "string",
|
|
5419
5586
|
enum: ["active", "deprecated"],
|
|
5420
5587
|
description: "Lifecycle state"
|
|
5588
|
+
},
|
|
5589
|
+
triggerType: {
|
|
5590
|
+
type: "string",
|
|
5591
|
+
enum: ["manual", "auto"],
|
|
5592
|
+
description: "Arm ('auto') or disarm ('manual') automatic application. Arming requires a rule to be present or supplied in the same call."
|
|
5593
|
+
},
|
|
5594
|
+
autoBind: {
|
|
5595
|
+
type: "object",
|
|
5596
|
+
description: "Replace the auto-bind rule: {priority?, mode?: 'all'|'any', when: [{path, op, value}]}. Pass null to remove it."
|
|
5421
5597
|
}
|
|
5422
5598
|
},
|
|
5423
5599
|
required: ["playbookId"]
|
|
@@ -5882,7 +6058,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
5882
6058
|
const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
|
|
5883
6059
|
const established = activeProjectId == null;
|
|
5884
6060
|
if (established) {
|
|
5885
|
-
deps.
|
|
6061
|
+
deps.setActiveContext({
|
|
6062
|
+
projectId: resolved.project.id,
|
|
6063
|
+
workspaceId: resolved.project.workspaceId
|
|
6064
|
+
});
|
|
5886
6065
|
}
|
|
5887
6066
|
return {
|
|
5888
6067
|
success: true,
|
|
@@ -6321,15 +6500,57 @@ ${options}
|
|
|
6321
6500
|
}
|
|
6322
6501
|
case "harmony_set_project_context": {
|
|
6323
6502
|
const projectId = z.string().uuid().parse(args.projectId);
|
|
6324
|
-
|
|
6325
|
-
|
|
6503
|
+
const explicitWorkspaceId = args.workspaceId ? z.string().uuid().parse(args.workspaceId) : null;
|
|
6504
|
+
let owningWorkspaceId = explicitWorkspaceId;
|
|
6505
|
+
if (!owningWorkspaceId) {
|
|
6506
|
+
try {
|
|
6507
|
+
const { workspaces } = await client3.listWorkspaces();
|
|
6508
|
+
for (const workspace of workspaces) {
|
|
6509
|
+
if (!workspace?.id)
|
|
6510
|
+
continue;
|
|
6511
|
+
const { projects } = await client3.listProjects(workspace.id);
|
|
6512
|
+
if (projects.some((p) => p?.id === projectId)) {
|
|
6513
|
+
owningWorkspaceId = workspace.id;
|
|
6514
|
+
break;
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
} catch (error) {
|
|
6518
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6519
|
+
return {
|
|
6520
|
+
success: false,
|
|
6521
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6522
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6523
|
+
note: `Could not resolve this project's workspace (${reason}), so the ` + `active context was left unchanged rather than half-written. ` + `Retry, or pass workspaceId explicitly to skip the lookup.`
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
if (!owningWorkspaceId) {
|
|
6527
|
+
return {
|
|
6528
|
+
success: false,
|
|
6529
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6530
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6531
|
+
note: `Project ${projectId} is not in any workspace this connection ` + `can reach, so the active context was left unchanged. Check ` + `harmony_list_projects, pass workspaceId explicitly, or ` + `reconnect with /mcp if it lives in another workspace.`
|
|
6532
|
+
};
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
deps.setActiveContext({ projectId, workspaceId: owningWorkspaceId });
|
|
6536
|
+
return {
|
|
6537
|
+
success: true,
|
|
6538
|
+
activeProjectId: projectId,
|
|
6539
|
+
activeWorkspaceId: owningWorkspaceId
|
|
6540
|
+
};
|
|
6326
6541
|
}
|
|
6327
6542
|
case "harmony_get_context": {
|
|
6543
|
+
const report = describeActiveContext({
|
|
6544
|
+
projectId: deps.getActiveProjectId(),
|
|
6545
|
+
workspaceId: deps.getActiveWorkspaceId()
|
|
6546
|
+
});
|
|
6328
6547
|
return {
|
|
6329
6548
|
success: true,
|
|
6330
6549
|
context: {
|
|
6331
|
-
activeWorkspaceId:
|
|
6332
|
-
activeProjectId:
|
|
6550
|
+
activeWorkspaceId: report.workspaceId,
|
|
6551
|
+
activeProjectId: report.projectId,
|
|
6552
|
+
consistent: report.consistent,
|
|
6553
|
+
...report.note ? { note: report.note } : {}
|
|
6333
6554
|
}
|
|
6334
6555
|
};
|
|
6335
6556
|
}
|
|
@@ -6769,7 +6990,7 @@ ${options}
|
|
|
6769
6990
|
if (trimmed.length > 0) {
|
|
6770
6991
|
const touchIds = trimmed.map(({ entity }) => entity?.id).filter((id) => typeof id === "string");
|
|
6771
6992
|
if (touchIds.length > 0) {
|
|
6772
|
-
client3.batchTouchMemoryEntities(touchIds).catch(() => {});
|
|
6993
|
+
client3.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
|
|
6773
6994
|
}
|
|
6774
6995
|
}
|
|
6775
6996
|
let sessionEntities = [];
|
|
@@ -7178,9 +7399,17 @@ ${options}
|
|
|
7178
7399
|
workspaceId,
|
|
7179
7400
|
name: name2,
|
|
7180
7401
|
description: args.description,
|
|
7181
|
-
steps: args.steps
|
|
7402
|
+
steps: args.steps,
|
|
7403
|
+
triggerType: args.triggerType,
|
|
7404
|
+
autoBind: args.autoBind,
|
|
7405
|
+
catalogId: args.catalogId
|
|
7182
7406
|
});
|
|
7183
|
-
|
|
7407
|
+
const warnings = await collectPlaybookMetricWarnings(client3, workspaceId, args.steps);
|
|
7408
|
+
return {
|
|
7409
|
+
success: true,
|
|
7410
|
+
playbook: result.playbook,
|
|
7411
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
7412
|
+
};
|
|
7184
7413
|
}
|
|
7185
7414
|
case "harmony_update_playbook": {
|
|
7186
7415
|
const playbookId = z.string().uuid().parse(args.playbookId);
|
|
@@ -7189,9 +7418,16 @@ ${options}
|
|
|
7189
7418
|
description: args.description,
|
|
7190
7419
|
steps: args.steps,
|
|
7191
7420
|
enabled: args.enabled,
|
|
7192
|
-
state: args.state
|
|
7421
|
+
state: args.state,
|
|
7422
|
+
triggerType: args.triggerType,
|
|
7423
|
+
..."autoBind" in args ? { autoBind: args.autoBind } : {}
|
|
7193
7424
|
});
|
|
7194
|
-
|
|
7425
|
+
const warnings = await collectPlaybookMetricWarnings(client3, result.playbook?.workspace_id, args.steps);
|
|
7426
|
+
return {
|
|
7427
|
+
success: true,
|
|
7428
|
+
playbook: result.playbook,
|
|
7429
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
7430
|
+
};
|
|
7195
7431
|
}
|
|
7196
7432
|
case "harmony_save_card_as_playbook":
|
|
7197
7433
|
return deprecatedRemovedToolResult("harmony_save_card_as_playbook");
|
|
@@ -7266,8 +7502,10 @@ ${options}
|
|
|
7266
7502
|
apiUrl: deps.getApiUrl()
|
|
7267
7503
|
});
|
|
7268
7504
|
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
7269
|
-
deps.
|
|
7270
|
-
|
|
7505
|
+
deps.setActiveContext({
|
|
7506
|
+
projectId: result.project.id,
|
|
7507
|
+
workspaceId: result.workspace.id
|
|
7508
|
+
});
|
|
7271
7509
|
deps.resetClient();
|
|
7272
7510
|
return {
|
|
7273
7511
|
success: true,
|
|
@@ -7289,7 +7527,7 @@ function createConfigDeps() {
|
|
|
7289
7527
|
isConfigured,
|
|
7290
7528
|
getActiveProjectId: () => getActiveProjectId(),
|
|
7291
7529
|
getActiveWorkspaceId: () => getActiveWorkspaceId(),
|
|
7292
|
-
|
|
7530
|
+
setActiveContext: (context) => setActiveContext(context),
|
|
7293
7531
|
setActiveWorkspace: (id) => setActiveWorkspace(id),
|
|
7294
7532
|
getApiUrl,
|
|
7295
7533
|
getMemoryDir: () => getMemoryDir(),
|