@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/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/config.ts
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
|
-
import { join } from "node:path";
|
|
23
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
24
24
|
function getConfigDir() {
|
|
25
25
|
return join(homedir(), ".harmony-mcp");
|
|
26
26
|
}
|
|
@@ -30,6 +30,22 @@ function getConfigPath() {
|
|
|
30
30
|
function getLocalConfigPath(cwd) {
|
|
31
31
|
return join(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 = join(dir, LOCAL_CONFIG_FILENAME);
|
|
40
|
+
if (existsSync(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
|
writeFileSync(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();
|
|
@@ -1386,6 +1380,9 @@ import {
|
|
|
1386
1380
|
ReadResourceRequestSchema
|
|
1387
1381
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1388
1382
|
import { z } from "zod";
|
|
1383
|
+
|
|
1384
|
+
// src/api-client.ts
|
|
1385
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1389
1386
|
// ../harmony-shared/dist/agentStaleness.js
|
|
1390
1387
|
var AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
|
|
1391
1388
|
var AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
|
|
@@ -1507,12 +1504,97 @@ var TIMINGS = {
|
|
|
1507
1504
|
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
1508
1505
|
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
1509
1506
|
};
|
|
1507
|
+
// ../harmony-shared/dist/declaredGateMetrics.js
|
|
1508
|
+
function declaredGateMetricsFromAgents(agents) {
|
|
1509
|
+
const names = new Set;
|
|
1510
|
+
let known = false;
|
|
1511
|
+
for (const agent of agents) {
|
|
1512
|
+
const declared = agent.declared_gate_metrics;
|
|
1513
|
+
if (!Array.isArray(declared))
|
|
1514
|
+
continue;
|
|
1515
|
+
known = true;
|
|
1516
|
+
for (const name of declared) {
|
|
1517
|
+
if (typeof name === "string" && name.trim())
|
|
1518
|
+
names.add(name.trim());
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return { names, known };
|
|
1522
|
+
}
|
|
1523
|
+
// ../harmony-shared/dist/gateConfigError.js
|
|
1524
|
+
var GATE_CONFIG_ERROR_KEY = "configError";
|
|
1525
|
+
var GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
|
|
1510
1526
|
// ../harmony-shared/dist/playbookStage.js
|
|
1527
|
+
var DEFAULT_LOOP_MAX_ITERATIONS = 5;
|
|
1528
|
+
function normalizeLoopDef(raw) {
|
|
1529
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
1530
|
+
return null;
|
|
1531
|
+
const obj = raw;
|
|
1532
|
+
if (obj.mode !== "converge" && obj.mode !== "fanout")
|
|
1533
|
+
return null;
|
|
1534
|
+
const mode = obj.mode;
|
|
1535
|
+
const rawMax = obj.max_iterations;
|
|
1536
|
+
const maxInt = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 1 ? Math.floor(rawMax) : DEFAULT_LOOP_MAX_ITERATIONS;
|
|
1537
|
+
const exitGate = obj.exit_gate && typeof obj.exit_gate === "object" && !Array.isArray(obj.exit_gate) ? obj.exit_gate : null;
|
|
1538
|
+
const def = { mode, max_iterations: maxInt };
|
|
1539
|
+
if (exitGate)
|
|
1540
|
+
def.exit_gate = exitGate;
|
|
1541
|
+
if (obj.item_source && typeof obj.item_source === "object" && !Array.isArray(obj.item_source)) {
|
|
1542
|
+
def.item_source = obj.item_source;
|
|
1543
|
+
}
|
|
1544
|
+
if (typeof obj.concurrency === "number" && obj.concurrency >= 1) {
|
|
1545
|
+
def.concurrency = Math.floor(obj.concurrency);
|
|
1546
|
+
}
|
|
1547
|
+
if (obj.on_item_fail === "continue" || obj.on_item_fail === "halt") {
|
|
1548
|
+
def.on_item_fail = obj.on_item_fail;
|
|
1549
|
+
}
|
|
1550
|
+
return def;
|
|
1551
|
+
}
|
|
1552
|
+
function readStageDefs(def) {
|
|
1553
|
+
if (def.steps_version !== 2)
|
|
1554
|
+
return [];
|
|
1555
|
+
return Array.isArray(def.steps) ? def.steps : [];
|
|
1556
|
+
}
|
|
1511
1557
|
var STAGE_DAEMON_OWNED_TOOLS = [
|
|
1512
1558
|
"mcp__harmony__harmony_end_agent_session",
|
|
1513
1559
|
"mcp__harmony__harmony_start_agent_session",
|
|
1514
1560
|
"mcp__harmony__harmony_move_card"
|
|
1515
1561
|
];
|
|
1562
|
+
function customGateMetric(gate) {
|
|
1563
|
+
if (gate === null || typeof gate !== "object" || Array.isArray(gate)) {
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
const record = gate;
|
|
1567
|
+
if (record.kind !== "custom")
|
|
1568
|
+
return null;
|
|
1569
|
+
if (record.pendingEngine === true)
|
|
1570
|
+
return null;
|
|
1571
|
+
const metric = typeof record.metric === "string" ? record.metric.trim() : "";
|
|
1572
|
+
return metric ? metric : null;
|
|
1573
|
+
}
|
|
1574
|
+
function referencedGateMetrics(def) {
|
|
1575
|
+
const out = [];
|
|
1576
|
+
for (const stage of readStageDefs(def)) {
|
|
1577
|
+
if (!stage || typeof stage !== "object")
|
|
1578
|
+
continue;
|
|
1579
|
+
const stageId = typeof stage.id === "string" ? stage.id : "";
|
|
1580
|
+
const stageName = typeof stage.name === "string" ? stage.name : stageId;
|
|
1581
|
+
const gateMetric = customGateMetric(stage.gate);
|
|
1582
|
+
if (gateMetric) {
|
|
1583
|
+
out.push({ stageId, stageName, metric: gateMetric, source: "gate" });
|
|
1584
|
+
}
|
|
1585
|
+
const loop = normalizeLoopDef(stage.loop);
|
|
1586
|
+
const loopMetric = loop?.exit_gate ? customGateMetric(loop.exit_gate) : null;
|
|
1587
|
+
if (loopMetric) {
|
|
1588
|
+
out.push({
|
|
1589
|
+
stageId,
|
|
1590
|
+
stageName,
|
|
1591
|
+
metric: loopMetric,
|
|
1592
|
+
source: "loop_exit_gate"
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return out;
|
|
1597
|
+
}
|
|
1516
1598
|
// ../harmony-shared/dist/reviewTools.js
|
|
1517
1599
|
var REVIEW_DISALLOWED_TOOLS = [
|
|
1518
1600
|
...STAGE_DAEMON_OWNED_TOOLS,
|
|
@@ -1546,7 +1628,20 @@ function getRetryDelay(attempt) {
|
|
|
1546
1628
|
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1547
1629
|
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1548
1630
|
}
|
|
1549
|
-
var sleep2 = (ms) => new Promise((
|
|
1631
|
+
var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1632
|
+
function buildMemoryQuery(title, description) {
|
|
1633
|
+
const DESCRIPTION_CAP = 600;
|
|
1634
|
+
const trimmedTitle = title.trim();
|
|
1635
|
+
const trimmedBody = (description ?? "").trim();
|
|
1636
|
+
if (!trimmedTitle && !trimmedBody)
|
|
1637
|
+
return "";
|
|
1638
|
+
if (!trimmedTitle)
|
|
1639
|
+
return trimmedBody.slice(0, DESCRIPTION_CAP);
|
|
1640
|
+
if (!trimmedBody)
|
|
1641
|
+
return trimmedTitle;
|
|
1642
|
+
return `${trimmedTitle}
|
|
1643
|
+
${trimmedBody.slice(0, DESCRIPTION_CAP)}`;
|
|
1644
|
+
}
|
|
1550
1645
|
|
|
1551
1646
|
class Semaphore {
|
|
1552
1647
|
permits;
|
|
@@ -1559,7 +1654,7 @@ class Semaphore {
|
|
|
1559
1654
|
this.permits--;
|
|
1560
1655
|
return;
|
|
1561
1656
|
}
|
|
1562
|
-
return new Promise((
|
|
1657
|
+
return new Promise((resolve2) => this.queue.push(resolve2));
|
|
1563
1658
|
}
|
|
1564
1659
|
release() {
|
|
1565
1660
|
const next = this.queue.shift();
|
|
@@ -2017,6 +2112,14 @@ class HarmonyApiClient {
|
|
|
2017
2112
|
params.set("sinceSeq", String(sinceSeq));
|
|
2018
2113
|
return this.request("GET", `/cards/${cardId}/agent-messages?${params.toString()}`);
|
|
2019
2114
|
}
|
|
2115
|
+
async postBudgetDecision(cardId, data) {
|
|
2116
|
+
return this.request("POST", `/cards/${cardId}/budget-decisions`, data);
|
|
2117
|
+
}
|
|
2118
|
+
async getBudgetDecisions(cardId, sinceIso) {
|
|
2119
|
+
const params = new URLSearchParams;
|
|
2120
|
+
params.set("sinceIso", sinceIso);
|
|
2121
|
+
return this.request("GET", `/cards/${cardId}/budget-decisions?${params.toString()}`);
|
|
2122
|
+
}
|
|
2020
2123
|
async updateAgentProgress(cardId, data) {
|
|
2021
2124
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
2022
2125
|
}
|
|
@@ -2062,6 +2165,8 @@ class HarmonyApiClient {
|
|
|
2062
2165
|
params.set("offset", String(options.offset));
|
|
2063
2166
|
if (options.include_superseded)
|
|
2064
2167
|
params.set("include_superseded", "true");
|
|
2168
|
+
if (options.consumer)
|
|
2169
|
+
params.set("consumer", options.consumer);
|
|
2065
2170
|
if (options.include_episodes)
|
|
2066
2171
|
params.set("include_episodes", "true");
|
|
2067
2172
|
return this.request("GET", `/memory/entities?${params.toString()}`);
|
|
@@ -2117,6 +2222,12 @@ class HarmonyApiClient {
|
|
|
2117
2222
|
if (options.topK !== undefined) {
|
|
2118
2223
|
entities = entities.slice(0, options.topK);
|
|
2119
2224
|
}
|
|
2225
|
+
if (options.consumer) {
|
|
2226
|
+
const deliveredIds = entities.map((e) => e.id).filter((id) => typeof id === "string");
|
|
2227
|
+
if (deliveredIds.length > 0) {
|
|
2228
|
+
this.batchTouchMemoryEntities(deliveredIds, options.consumer).catch(() => {});
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2120
2231
|
return { entities };
|
|
2121
2232
|
}
|
|
2122
2233
|
async deleteMemoryEntity(entityId) {
|
|
@@ -2125,9 +2236,10 @@ class HarmonyApiClient {
|
|
|
2125
2236
|
async touchMemoryEntity(entityId) {
|
|
2126
2237
|
return this.request("POST", `/memory/entities/${entityId}/touch`);
|
|
2127
2238
|
}
|
|
2128
|
-
async batchTouchMemoryEntities(entityIds) {
|
|
2239
|
+
async batchTouchMemoryEntities(entityIds, consumer) {
|
|
2129
2240
|
return this.request("POST", "/memory/entities/batch-touch", {
|
|
2130
|
-
entity_ids: entityIds
|
|
2241
|
+
entity_ids: entityIds,
|
|
2242
|
+
...consumer ? { consumer } : {}
|
|
2131
2243
|
});
|
|
2132
2244
|
}
|
|
2133
2245
|
async createMemoryRelation(data) {
|
|
@@ -2153,8 +2265,12 @@ class HarmonyApiClient {
|
|
|
2153
2265
|
params.append("tags", tag);
|
|
2154
2266
|
if (options?.include_superseded)
|
|
2155
2267
|
params.set("include_superseded", "true");
|
|
2268
|
+
if (options?.consumer)
|
|
2269
|
+
params.set("consumer", options.consumer);
|
|
2156
2270
|
if (options?.include_episodes)
|
|
2157
2271
|
params.set("include_episodes", "true");
|
|
2272
|
+
if (options?.assembly_id)
|
|
2273
|
+
params.set("assembly_id", options.assembly_id);
|
|
2158
2274
|
return this.request("GET", `/memory/search?${params.toString()}`);
|
|
2159
2275
|
}
|
|
2160
2276
|
async getVaultIndex(options) {
|
|
@@ -2166,6 +2282,8 @@ class HarmonyApiClient {
|
|
|
2166
2282
|
params.set("type", options.type);
|
|
2167
2283
|
if (options.limit !== undefined)
|
|
2168
2284
|
params.set("limit", String(options.limit));
|
|
2285
|
+
if (options.consumer)
|
|
2286
|
+
params.set("consumer", options.consumer);
|
|
2169
2287
|
if (options.include_episodes)
|
|
2170
2288
|
params.set("include_episodes", "true");
|
|
2171
2289
|
return this.request("GET", `/memory/index?${params.toString()}`);
|
|
@@ -2179,6 +2297,8 @@ class HarmonyApiClient {
|
|
|
2179
2297
|
params.set("type", options.type);
|
|
2180
2298
|
if (options.limit !== undefined)
|
|
2181
2299
|
params.set("limit", String(options.limit));
|
|
2300
|
+
if (options.consumer)
|
|
2301
|
+
params.set("consumer", options.consumer);
|
|
2182
2302
|
if (options.include_episodes)
|
|
2183
2303
|
params.set("include_episodes", "true");
|
|
2184
2304
|
return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
|
|
@@ -2238,6 +2358,8 @@ class HarmonyApiClient {
|
|
|
2238
2358
|
params.set("type", options.type);
|
|
2239
2359
|
if (options?.limit !== undefined)
|
|
2240
2360
|
params.set("limit", String(options.limit));
|
|
2361
|
+
if (options?.consumer)
|
|
2362
|
+
params.set("consumer", options.consumer);
|
|
2241
2363
|
if (options?.include_episodes)
|
|
2242
2364
|
params.set("include_episodes", "true");
|
|
2243
2365
|
return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
|
|
@@ -2343,14 +2465,15 @@ class HarmonyApiClient {
|
|
|
2343
2465
|
} catch {}
|
|
2344
2466
|
}
|
|
2345
2467
|
const variant = options.variant || "execute";
|
|
2346
|
-
const
|
|
2347
|
-
const assemblyId = undefined;
|
|
2468
|
+
const assemblyId = randomUUID2();
|
|
2348
2469
|
let memories;
|
|
2349
2470
|
try {
|
|
2350
2471
|
if (options.workspaceId && cardData.title) {
|
|
2351
|
-
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
2472
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, buildMemoryQuery(cardData.title, cardData.description), {
|
|
2352
2473
|
project_id: options.projectId,
|
|
2353
|
-
limit: 5
|
|
2474
|
+
limit: 5,
|
|
2475
|
+
consumer: "agent-prompt",
|
|
2476
|
+
assembly_id: assemblyId
|
|
2354
2477
|
});
|
|
2355
2478
|
if (memoryResult.entities?.length > 0) {
|
|
2356
2479
|
memories = memoryResult.entities.map((e) => ({
|
|
@@ -2374,7 +2497,6 @@ class HarmonyApiClient {
|
|
|
2374
2497
|
contextOptions: options.contextOptions,
|
|
2375
2498
|
customConstraints: options.customConstraints,
|
|
2376
2499
|
memories,
|
|
2377
|
-
assembledContext: assembledContextStr,
|
|
2378
2500
|
assemblyId
|
|
2379
2501
|
});
|
|
2380
2502
|
try {
|
|
@@ -2705,7 +2827,7 @@ async function autoExpandGraph(client3, entityId, title, content, _tags, workspa
|
|
|
2705
2827
|
});
|
|
2706
2828
|
candidates = entities.filter((e) => e.id !== entityId && (e.confidence ?? 1) >= 0.4).slice(0, maxRelations);
|
|
2707
2829
|
if (candidates.length === 0) {
|
|
2708
|
-
await new Promise((
|
|
2830
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
2709
2831
|
const retry = await client3.searchMemoryEntities(workspaceId, query, {
|
|
2710
2832
|
project_id: projectId,
|
|
2711
2833
|
limit: 20
|
|
@@ -3288,6 +3410,34 @@ async function onboardNewUser(params) {
|
|
|
3288
3410
|
};
|
|
3289
3411
|
}
|
|
3290
3412
|
|
|
3413
|
+
// src/playbook-metric-warnings.ts
|
|
3414
|
+
function playbookMetricWarnings(agents, steps) {
|
|
3415
|
+
if (!Array.isArray(steps))
|
|
3416
|
+
return [];
|
|
3417
|
+
const declared = declaredGateMetricsFromAgents(agents);
|
|
3418
|
+
if (!declared.known)
|
|
3419
|
+
return [];
|
|
3420
|
+
const warnings = [];
|
|
3421
|
+
const seen = new Set;
|
|
3422
|
+
for (const ref of referencedGateMetrics({ steps, steps_version: 2 })) {
|
|
3423
|
+
if (declared.names.has(ref.metric) || seen.has(ref.metric))
|
|
3424
|
+
continue;
|
|
3425
|
+
seen.add(ref.metric);
|
|
3426
|
+
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}.`);
|
|
3427
|
+
}
|
|
3428
|
+
return warnings;
|
|
3429
|
+
}
|
|
3430
|
+
async function collectPlaybookMetricWarnings(client3, workspaceId, steps) {
|
|
3431
|
+
if (!workspaceId || !Array.isArray(steps))
|
|
3432
|
+
return [];
|
|
3433
|
+
try {
|
|
3434
|
+
const { agents } = await client3.listWorkspaceAgents(workspaceId);
|
|
3435
|
+
return playbookMetricWarnings(agents, steps);
|
|
3436
|
+
} catch {
|
|
3437
|
+
return [];
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3291
3441
|
// src/skills.ts
|
|
3292
3442
|
import {
|
|
3293
3443
|
existsSync as existsSync4,
|
|
@@ -3297,7 +3447,7 @@ import {
|
|
|
3297
3447
|
writeFileSync as writeFileSync3
|
|
3298
3448
|
} from "node:fs";
|
|
3299
3449
|
import { homedir as homedir3 } from "node:os";
|
|
3300
|
-
import { dirname, join as join5 } from "node:path";
|
|
3450
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
3301
3451
|
init_config();
|
|
3302
3452
|
|
|
3303
3453
|
// src/hmy-config.ts
|
|
@@ -3444,7 +3594,7 @@ function stripSkillPreamble(content) {
|
|
|
3444
3594
|
`;
|
|
3445
3595
|
}
|
|
3446
3596
|
function atomicWrite(filePath, content) {
|
|
3447
|
-
const dir =
|
|
3597
|
+
const dir = dirname2(filePath);
|
|
3448
3598
|
if (!existsSync4(dir))
|
|
3449
3599
|
mkdirSync3(dir, { recursive: true });
|
|
3450
3600
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -3535,10 +3685,10 @@ async function refreshSkills(opts = {}) {
|
|
|
3535
3685
|
continue;
|
|
3536
3686
|
let siblingPath;
|
|
3537
3687
|
if (samplePath.endsWith("SKILL.md")) {
|
|
3538
|
-
const parentDir =
|
|
3688
|
+
const parentDir = dirname2(dirname2(samplePath));
|
|
3539
3689
|
siblingPath = `${parentDir}/${name}/SKILL.md`;
|
|
3540
3690
|
} else {
|
|
3541
|
-
const parentDir =
|
|
3691
|
+
const parentDir = dirname2(samplePath);
|
|
3542
3692
|
siblingPath = `${parentDir}/${name}.md`;
|
|
3543
3693
|
}
|
|
3544
3694
|
if (existsSync4(siblingPath)) {
|
|
@@ -4332,7 +4482,7 @@ var TOOLS = {
|
|
|
4332
4482
|
}
|
|
4333
4483
|
},
|
|
4334
4484
|
harmony_classify_card: {
|
|
4335
|
-
description: "
|
|
4485
|
+
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`.",
|
|
4336
4486
|
inputSchema: {
|
|
4337
4487
|
type: "object",
|
|
4338
4488
|
properties: {
|
|
@@ -4586,11 +4736,15 @@ var TOOLS = {
|
|
|
4586
4736
|
}
|
|
4587
4737
|
},
|
|
4588
4738
|
harmony_set_project_context: {
|
|
4589
|
-
description: "Set the active project context for subsequent operations",
|
|
4739
|
+
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.",
|
|
4590
4740
|
inputSchema: {
|
|
4591
4741
|
type: "object",
|
|
4592
4742
|
properties: {
|
|
4593
|
-
projectId: { type: "string" }
|
|
4743
|
+
projectId: { type: "string" },
|
|
4744
|
+
workspaceId: {
|
|
4745
|
+
type: "string",
|
|
4746
|
+
description: "The workspace this project belongs to. Optional — looked up when omitted."
|
|
4747
|
+
}
|
|
4594
4748
|
},
|
|
4595
4749
|
required: ["projectId"]
|
|
4596
4750
|
}
|
|
@@ -5394,13 +5548,26 @@ var TOOLS = {
|
|
|
5394
5548
|
type: "array",
|
|
5395
5549
|
description: "The playbook's ordered stage objects.",
|
|
5396
5550
|
items: { type: "object" }
|
|
5551
|
+
},
|
|
5552
|
+
triggerType: {
|
|
5553
|
+
type: "string",
|
|
5554
|
+
enum: ["manual", "auto"],
|
|
5555
|
+
description: "'manual' (default) — the playbook is applied by a person. 'auto' — it claims matching cards itself, and requires autoBind."
|
|
5556
|
+
},
|
|
5557
|
+
autoBind: {
|
|
5558
|
+
type: "object",
|
|
5559
|
+
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."
|
|
5560
|
+
},
|
|
5561
|
+
catalogId: {
|
|
5562
|
+
type: "string",
|
|
5563
|
+
description: "Slug of the built-in template this came from (provenance only; never used to match)."
|
|
5397
5564
|
}
|
|
5398
5565
|
},
|
|
5399
5566
|
required: ["name"]
|
|
5400
5567
|
}
|
|
5401
5568
|
},
|
|
5402
5569
|
harmony_update_playbook: {
|
|
5403
|
-
description: "Update a playbook's name, description, steps/stages, enabled flag,
|
|
5570
|
+
description: "Update a playbook's name, description, steps/stages, enabled flag, lifecycle state ('active'|'deprecated'), or its auto-bind rule and arming.",
|
|
5404
5571
|
inputSchema: {
|
|
5405
5572
|
type: "object",
|
|
5406
5573
|
properties: {
|
|
@@ -5423,6 +5590,15 @@ var TOOLS = {
|
|
|
5423
5590
|
type: "string",
|
|
5424
5591
|
enum: ["active", "deprecated"],
|
|
5425
5592
|
description: "Lifecycle state"
|
|
5593
|
+
},
|
|
5594
|
+
triggerType: {
|
|
5595
|
+
type: "string",
|
|
5596
|
+
enum: ["manual", "auto"],
|
|
5597
|
+
description: "Arm ('auto') or disarm ('manual') automatic application. Arming requires a rule to be present or supplied in the same call."
|
|
5598
|
+
},
|
|
5599
|
+
autoBind: {
|
|
5600
|
+
type: "object",
|
|
5601
|
+
description: "Replace the auto-bind rule: {priority?, mode?: 'all'|'any', when: [{path, op, value}]}. Pass null to remove it."
|
|
5426
5602
|
}
|
|
5427
5603
|
},
|
|
5428
5604
|
required: ["playbookId"]
|
|
@@ -5887,7 +6063,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
5887
6063
|
const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
|
|
5888
6064
|
const established = activeProjectId == null;
|
|
5889
6065
|
if (established) {
|
|
5890
|
-
deps.
|
|
6066
|
+
deps.setActiveContext({
|
|
6067
|
+
projectId: resolved.project.id,
|
|
6068
|
+
workspaceId: resolved.project.workspaceId
|
|
6069
|
+
});
|
|
5891
6070
|
}
|
|
5892
6071
|
return {
|
|
5893
6072
|
success: true,
|
|
@@ -6326,15 +6505,57 @@ ${options}
|
|
|
6326
6505
|
}
|
|
6327
6506
|
case "harmony_set_project_context": {
|
|
6328
6507
|
const projectId = z.string().uuid().parse(args.projectId);
|
|
6329
|
-
|
|
6330
|
-
|
|
6508
|
+
const explicitWorkspaceId = args.workspaceId ? z.string().uuid().parse(args.workspaceId) : null;
|
|
6509
|
+
let owningWorkspaceId = explicitWorkspaceId;
|
|
6510
|
+
if (!owningWorkspaceId) {
|
|
6511
|
+
try {
|
|
6512
|
+
const { workspaces } = await client3.listWorkspaces();
|
|
6513
|
+
for (const workspace of workspaces) {
|
|
6514
|
+
if (!workspace?.id)
|
|
6515
|
+
continue;
|
|
6516
|
+
const { projects } = await client3.listProjects(workspace.id);
|
|
6517
|
+
if (projects.some((p) => p?.id === projectId)) {
|
|
6518
|
+
owningWorkspaceId = workspace.id;
|
|
6519
|
+
break;
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
} catch (error) {
|
|
6523
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6524
|
+
return {
|
|
6525
|
+
success: false,
|
|
6526
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6527
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6528
|
+
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.`
|
|
6529
|
+
};
|
|
6530
|
+
}
|
|
6531
|
+
if (!owningWorkspaceId) {
|
|
6532
|
+
return {
|
|
6533
|
+
success: false,
|
|
6534
|
+
activeProjectId: deps.getActiveProjectId(),
|
|
6535
|
+
activeWorkspaceId: deps.getActiveWorkspaceId(),
|
|
6536
|
+
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.`
|
|
6537
|
+
};
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
deps.setActiveContext({ projectId, workspaceId: owningWorkspaceId });
|
|
6541
|
+
return {
|
|
6542
|
+
success: true,
|
|
6543
|
+
activeProjectId: projectId,
|
|
6544
|
+
activeWorkspaceId: owningWorkspaceId
|
|
6545
|
+
};
|
|
6331
6546
|
}
|
|
6332
6547
|
case "harmony_get_context": {
|
|
6548
|
+
const report = describeActiveContext({
|
|
6549
|
+
projectId: deps.getActiveProjectId(),
|
|
6550
|
+
workspaceId: deps.getActiveWorkspaceId()
|
|
6551
|
+
});
|
|
6333
6552
|
return {
|
|
6334
6553
|
success: true,
|
|
6335
6554
|
context: {
|
|
6336
|
-
activeWorkspaceId:
|
|
6337
|
-
activeProjectId:
|
|
6555
|
+
activeWorkspaceId: report.workspaceId,
|
|
6556
|
+
activeProjectId: report.projectId,
|
|
6557
|
+
consistent: report.consistent,
|
|
6558
|
+
...report.note ? { note: report.note } : {}
|
|
6338
6559
|
}
|
|
6339
6560
|
};
|
|
6340
6561
|
}
|
|
@@ -6774,7 +6995,7 @@ ${options}
|
|
|
6774
6995
|
if (trimmed.length > 0) {
|
|
6775
6996
|
const touchIds = trimmed.map(({ entity }) => entity?.id).filter((id) => typeof id === "string");
|
|
6776
6997
|
if (touchIds.length > 0) {
|
|
6777
|
-
client3.batchTouchMemoryEntities(touchIds).catch(() => {});
|
|
6998
|
+
client3.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
|
|
6778
6999
|
}
|
|
6779
7000
|
}
|
|
6780
7001
|
let sessionEntities = [];
|
|
@@ -7183,9 +7404,17 @@ ${options}
|
|
|
7183
7404
|
workspaceId,
|
|
7184
7405
|
name: name2,
|
|
7185
7406
|
description: args.description,
|
|
7186
|
-
steps: args.steps
|
|
7407
|
+
steps: args.steps,
|
|
7408
|
+
triggerType: args.triggerType,
|
|
7409
|
+
autoBind: args.autoBind,
|
|
7410
|
+
catalogId: args.catalogId
|
|
7187
7411
|
});
|
|
7188
|
-
|
|
7412
|
+
const warnings = await collectPlaybookMetricWarnings(client3, workspaceId, args.steps);
|
|
7413
|
+
return {
|
|
7414
|
+
success: true,
|
|
7415
|
+
playbook: result.playbook,
|
|
7416
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
7417
|
+
};
|
|
7189
7418
|
}
|
|
7190
7419
|
case "harmony_update_playbook": {
|
|
7191
7420
|
const playbookId = z.string().uuid().parse(args.playbookId);
|
|
@@ -7194,9 +7423,16 @@ ${options}
|
|
|
7194
7423
|
description: args.description,
|
|
7195
7424
|
steps: args.steps,
|
|
7196
7425
|
enabled: args.enabled,
|
|
7197
|
-
state: args.state
|
|
7426
|
+
state: args.state,
|
|
7427
|
+
triggerType: args.triggerType,
|
|
7428
|
+
..."autoBind" in args ? { autoBind: args.autoBind } : {}
|
|
7198
7429
|
});
|
|
7199
|
-
|
|
7430
|
+
const warnings = await collectPlaybookMetricWarnings(client3, result.playbook?.workspace_id, args.steps);
|
|
7431
|
+
return {
|
|
7432
|
+
success: true,
|
|
7433
|
+
playbook: result.playbook,
|
|
7434
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
7435
|
+
};
|
|
7200
7436
|
}
|
|
7201
7437
|
case "harmony_save_card_as_playbook":
|
|
7202
7438
|
return deprecatedRemovedToolResult("harmony_save_card_as_playbook");
|
|
@@ -7271,8 +7507,10 @@ ${options}
|
|
|
7271
7507
|
apiUrl: deps.getApiUrl()
|
|
7272
7508
|
});
|
|
7273
7509
|
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
7274
|
-
deps.
|
|
7275
|
-
|
|
7510
|
+
deps.setActiveContext({
|
|
7511
|
+
projectId: result.project.id,
|
|
7512
|
+
workspaceId: result.workspace.id
|
|
7513
|
+
});
|
|
7276
7514
|
deps.resetClient();
|
|
7277
7515
|
return {
|
|
7278
7516
|
success: true,
|
|
@@ -7294,7 +7532,7 @@ function createConfigDeps() {
|
|
|
7294
7532
|
isConfigured,
|
|
7295
7533
|
getActiveProjectId: () => getActiveProjectId(),
|
|
7296
7534
|
getActiveWorkspaceId: () => getActiveWorkspaceId(),
|
|
7297
|
-
|
|
7535
|
+
setActiveContext: (context) => setActiveContext(context),
|
|
7298
7536
|
setActiveWorkspace: (id) => setActiveWorkspace(id),
|
|
7299
7537
|
getApiUrl,
|
|
7300
7538
|
getMemoryDir: () => getMemoryDir(),
|
|
@@ -7373,7 +7611,7 @@ import {
|
|
|
7373
7611
|
unlinkSync
|
|
7374
7612
|
} from "node:fs";
|
|
7375
7613
|
import { homedir as homedir6 } from "node:os";
|
|
7376
|
-
import { dirname as
|
|
7614
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
7377
7615
|
import * as p4 from "@clack/prompts";
|
|
7378
7616
|
init_config();
|
|
7379
7617
|
init_oauth_login();
|
|
@@ -7445,7 +7683,7 @@ async function confirmOrDefault(assumeYes, opts) {
|
|
|
7445
7683
|
|
|
7446
7684
|
// src/tui/docs.ts
|
|
7447
7685
|
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
|
|
7448
|
-
import { isAbsolute, join as join7, resolve, sep as sep2 } from "node:path";
|
|
7686
|
+
import { isAbsolute, join as join7, resolve as resolve2, sep as sep2 } from "node:path";
|
|
7449
7687
|
import * as p2 from "@clack/prompts";
|
|
7450
7688
|
|
|
7451
7689
|
// src/tui/theme.ts
|
|
@@ -7869,7 +8107,7 @@ function verifyDocs(cwd) {
|
|
|
7869
8107
|
const agentsMd = readText(join7(cwd, "AGENTS.md"));
|
|
7870
8108
|
const pkg = readJson(join7(cwd, "package.json"));
|
|
7871
8109
|
const pkgScripts = pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
7872
|
-
const projectRoot =
|
|
8110
|
+
const projectRoot = resolve2(cwd);
|
|
7873
8111
|
if (claudeMd) {
|
|
7874
8112
|
const importedFiles = [];
|
|
7875
8113
|
for (const line of claudeMd.split(`
|
|
@@ -7886,7 +8124,7 @@ function verifyDocs(cwd) {
|
|
|
7886
8124
|
});
|
|
7887
8125
|
continue;
|
|
7888
8126
|
}
|
|
7889
|
-
const resolvedPath =
|
|
8127
|
+
const resolvedPath = resolve2(projectRoot, refPath);
|
|
7890
8128
|
if (resolvedPath !== projectRoot && !resolvedPath.startsWith(projectRoot + sep2)) {
|
|
7891
8129
|
issues.push({
|
|
7892
8130
|
severity: "error",
|
|
@@ -8071,13 +8309,13 @@ function checkBacktickPaths(content, file, cwd, issues) {
|
|
|
8071
8309
|
const pathRe = /`((?:src\/|packages\/|apps\/|supabase\/|docs\/)[^`]+)`/g;
|
|
8072
8310
|
let match;
|
|
8073
8311
|
const checked = new Set;
|
|
8074
|
-
const root =
|
|
8312
|
+
const root = resolve2(cwd);
|
|
8075
8313
|
while ((match = pathRe.exec(content)) !== null) {
|
|
8076
8314
|
const refPath = match[1].replace(/\/$/, "");
|
|
8077
8315
|
if (checked.has(refPath))
|
|
8078
8316
|
continue;
|
|
8079
8317
|
checked.add(refPath);
|
|
8080
|
-
const resolvedRef =
|
|
8318
|
+
const resolvedRef = resolve2(root, refPath);
|
|
8081
8319
|
if (resolvedRef !== root && !resolvedRef.startsWith(root + sep2))
|
|
8082
8320
|
continue;
|
|
8083
8321
|
if (!existsSync6(resolvedRef)) {
|
|
@@ -8158,7 +8396,7 @@ import {
|
|
|
8158
8396
|
writeFileSync as writeFileSync4
|
|
8159
8397
|
} from "node:fs";
|
|
8160
8398
|
import { homedir as homedir5 } from "node:os";
|
|
8161
|
-
import { dirname as
|
|
8399
|
+
import { dirname as dirname3 } from "node:path";
|
|
8162
8400
|
import * as p3 from "@clack/prompts";
|
|
8163
8401
|
function ensureDir(dirPath) {
|
|
8164
8402
|
if (!existsSync7(dirPath)) {
|
|
@@ -8171,7 +8409,7 @@ function writeFile(filePath, content, options = {}) {
|
|
|
8171
8409
|
return { path: filePath, action: "skip" };
|
|
8172
8410
|
}
|
|
8173
8411
|
try {
|
|
8174
|
-
ensureDir(
|
|
8412
|
+
ensureDir(dirname3(filePath));
|
|
8175
8413
|
const defaultMode = filePath.includes(".harmony-mcp") ? 384 : 420;
|
|
8176
8414
|
const mode = options.mode ?? defaultMode;
|
|
8177
8415
|
writeFileSync4(filePath, content, { mode });
|
|
@@ -8191,7 +8429,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
|
|
|
8191
8429
|
const exists = existsSync7(filePath);
|
|
8192
8430
|
if (!exists) {
|
|
8193
8431
|
try {
|
|
8194
|
-
ensureDir(
|
|
8432
|
+
ensureDir(dirname3(filePath));
|
|
8195
8433
|
writeFileSync4(filePath, JSON.stringify(updates, null, 2), {
|
|
8196
8434
|
mode: 420
|
|
8197
8435
|
});
|
|
@@ -8241,7 +8479,7 @@ function appendToToml(filePath, section, content, options = {}) {
|
|
|
8241
8479
|
const exists = existsSync7(filePath);
|
|
8242
8480
|
if (!exists) {
|
|
8243
8481
|
try {
|
|
8244
|
-
ensureDir(
|
|
8482
|
+
ensureDir(dirname3(filePath));
|
|
8245
8483
|
writeFileSync4(filePath, content, { mode: 420 });
|
|
8246
8484
|
return { path: filePath, action: "create" };
|
|
8247
8485
|
} catch (error) {
|
|
@@ -8295,7 +8533,7 @@ async function writeFilesWithProgress(files, options = {}) {
|
|
|
8295
8533
|
});
|
|
8296
8534
|
}
|
|
8297
8535
|
results.push(result);
|
|
8298
|
-
await new Promise((
|
|
8536
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
8299
8537
|
}
|
|
8300
8538
|
spinner2.stop("Files written");
|
|
8301
8539
|
for (const result of results) {
|
|
@@ -8395,7 +8633,7 @@ async function registerMcpServer() {
|
|
|
8395
8633
|
async function writeMcpConfigFallback(home) {
|
|
8396
8634
|
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
8397
8635
|
const settingsPath = join8(home, ".claude", "settings.json");
|
|
8398
|
-
const settingsDir =
|
|
8636
|
+
const settingsDir = dirname4(settingsPath);
|
|
8399
8637
|
if (!existsSync9(settingsDir)) {
|
|
8400
8638
|
mkdirSync6(settingsDir, { recursive: true });
|
|
8401
8639
|
}
|
|
@@ -8414,7 +8652,7 @@ async function writeMcpConfigFallback(home) {
|
|
|
8414
8652
|
async function allowlistHarmonyTools(home, allowAll) {
|
|
8415
8653
|
const { readFileSync: readFileSync7, writeFileSync: writeFileSync5, mkdirSync: mkdirSync6, existsSync: existsSync9 } = await import("node:fs");
|
|
8416
8654
|
const settingsPath = join8(home, ".claude", "settings.json");
|
|
8417
|
-
const settingsDir =
|
|
8655
|
+
const settingsDir = dirname4(settingsPath);
|
|
8418
8656
|
if (!existsSync9(settingsDir)) {
|
|
8419
8657
|
mkdirSync6(settingsDir, { recursive: true });
|
|
8420
8658
|
}
|
|
@@ -8946,8 +9184,10 @@ ${colors.dim(url)}`);
|
|
|
8946
9184
|
createdNewAccount = true;
|
|
8947
9185
|
needsApiKey = true;
|
|
8948
9186
|
saveConfig({ apiKey, userEmail, apiUrl: API_URL });
|
|
8949
|
-
|
|
8950
|
-
|
|
9187
|
+
setActiveContext({
|
|
9188
|
+
workspaceId: selectedWorkspaceIdFromSignup,
|
|
9189
|
+
projectId: selectedProjectIdFromSignup
|
|
9190
|
+
});
|
|
8951
9191
|
p4.log.success("Workspace and board created");
|
|
8952
9192
|
} catch (error) {
|
|
8953
9193
|
spinner4.stop(colors.error("Account creation failed"));
|
|
@@ -9279,7 +9519,7 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
9279
9519
|
if (allSymlinks.length > 0) {
|
|
9280
9520
|
for (const symlink of allSymlinks) {
|
|
9281
9521
|
try {
|
|
9282
|
-
const linkDir =
|
|
9522
|
+
const linkDir = dirname4(symlink.link);
|
|
9283
9523
|
if (!existsSync8(linkDir)) {
|
|
9284
9524
|
mkdirSync5(linkDir, { recursive: true });
|
|
9285
9525
|
}
|
|
@@ -9346,10 +9586,12 @@ Specify the workspace with --workspace <id>, or select one below.`);
|
|
|
9346
9586
|
localConfig.projectId = selectedProjectId;
|
|
9347
9587
|
saveLocalConfig(localConfig, cwd);
|
|
9348
9588
|
console.log(` ${colors.success("✓")} ${colors.dim(formatPath(getLocalConfigPath(cwd), home))} ${colors.dim("(created)")}`);
|
|
9349
|
-
if (selectedWorkspaceId)
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9589
|
+
if (selectedWorkspaceId || selectedProjectId) {
|
|
9590
|
+
setActiveContext({
|
|
9591
|
+
workspaceId: selectedWorkspaceId ?? null,
|
|
9592
|
+
projectId: selectedProjectId ?? null
|
|
9593
|
+
}, { global: true });
|
|
9594
|
+
}
|
|
9353
9595
|
}
|
|
9354
9596
|
console.log("");
|
|
9355
9597
|
p4.outro(colors.success("Setup complete!"));
|
|
@@ -9448,7 +9690,7 @@ Skills:`);
|
|
|
9448
9690
|
console.log(`
|
|
9449
9691
|
Context:`);
|
|
9450
9692
|
if (hasLocal) {
|
|
9451
|
-
console.log(` Local config: ${
|
|
9693
|
+
console.log(` Local config: ${findLocalConfigPath()}`);
|
|
9452
9694
|
console.log(` Workspace: ${localConfig?.workspaceId || "(not set)"}`);
|
|
9453
9695
|
console.log(` Project: ${localConfig?.projectId || "(not set)"}`);
|
|
9454
9696
|
}
|
|
@@ -9457,12 +9699,18 @@ Context:`);
|
|
|
9457
9699
|
console.log(` Project: ${globalConfig.activeProjectId || "(not set)"}`);
|
|
9458
9700
|
const effectiveWorkspace = getActiveWorkspaceId();
|
|
9459
9701
|
const effectiveProject = getActiveProjectId();
|
|
9460
|
-
const
|
|
9461
|
-
const
|
|
9702
|
+
const contextSource = hasLocal ? "local" : "global";
|
|
9703
|
+
const wsSource = effectiveWorkspace ? contextSource : "";
|
|
9704
|
+
const projSource = effectiveProject ? contextSource : "";
|
|
9462
9705
|
console.log(`
|
|
9463
9706
|
Active (effective):`);
|
|
9464
9707
|
console.log(` Workspace: ${effectiveWorkspace || "(not set)"}${wsSource ? ` ← ${wsSource}` : ""}`);
|
|
9465
9708
|
console.log(` Project: ${effectiveProject || "(not set)"}${projSource ? ` ← ${projSource}` : ""}`);
|
|
9709
|
+
const report = getActiveContext();
|
|
9710
|
+
if (!report.consistent && report.note) {
|
|
9711
|
+
console.log(`
|
|
9712
|
+
⚠ ${report.note}`);
|
|
9713
|
+
}
|
|
9466
9714
|
} else {
|
|
9467
9715
|
console.log(`Status: Not configured
|
|
9468
9716
|
`);
|