@gethmy/mcp 2.23.0 → 2.24.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/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 = getLocalConfigPath(cwd);
85
- if (!existsSync2(localConfigPath)) {
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 existsSync2(getLocalConfigPath(cwd));
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 setActiveWorkspace(workspaceId, options) {
137
- if (options?.local) {
138
- saveLocalConfig({ workspaceId }, options.cwd);
139
- } else {
140
- saveConfig({ activeWorkspaceId: workspaceId });
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
- function setActiveProject(projectId, options) {
144
- if (options?.local) {
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({ activeProjectId: projectId });
164
+ saveConfig({
165
+ activeWorkspaceId: context.workspaceId,
166
+ activeProjectId: context.projectId
167
+ });
148
168
  }
149
169
  }
150
- function getActiveWorkspaceId(cwd) {
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?.workspaceId) {
153
- return localConfig.workspaceId;
180
+ if (localConfig) {
181
+ return {
182
+ workspaceId: localConfig.workspaceId ?? null,
183
+ projectId: localConfig.projectId ?? null
184
+ };
154
185
  }
155
- return loadConfig().activeWorkspaceId;
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
- const localConfig = loadLocalConfig(cwd);
159
- if (localConfig?.projectId) {
160
- return localConfig.projectId;
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 loadConfig().activeProjectId;
214
+ return { projectId, workspaceId, consistent: true, note: null };
163
215
  }
164
216
  function isConfigured() {
165
217
  const config = loadConfig();
@@ -878,7 +930,7 @@ function lockPath() {
878
930
  return join3(getConfigDir(), LOCK_FILENAME);
879
931
  }
880
932
  function sleep(ms) {
881
- return new Promise((resolve) => setTimeout(resolve, ms));
933
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
882
934
  }
883
935
  async function withRefreshLock(fn) {
884
936
  const path = lockPath();
@@ -1541,7 +1593,7 @@ function getRetryDelay(attempt) {
1541
1593
  const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
1542
1594
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
1543
1595
  }
1544
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1596
+ var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1545
1597
 
1546
1598
  class Semaphore {
1547
1599
  permits;
@@ -1554,7 +1606,7 @@ class Semaphore {
1554
1606
  this.permits--;
1555
1607
  return;
1556
1608
  }
1557
- return new Promise((resolve) => this.queue.push(resolve));
1609
+ return new Promise((resolve2) => this.queue.push(resolve2));
1558
1610
  }
1559
1611
  release() {
1560
1612
  const next = this.queue.shift();
@@ -2700,7 +2752,7 @@ async function autoExpandGraph(client3, entityId, title, content, _tags, workspa
2700
2752
  });
2701
2753
  candidates = entities.filter((e) => e.id !== entityId && (e.confidence ?? 1) >= 0.4).slice(0, maxRelations);
2702
2754
  if (candidates.length === 0) {
2703
- await new Promise((resolve) => setTimeout(resolve, 2000));
2755
+ await new Promise((resolve2) => setTimeout(resolve2, 2000));
2704
2756
  const retry = await client3.searchMemoryEntities(workspaceId, query, {
2705
2757
  project_id: projectId,
2706
2758
  limit: 20
@@ -3292,7 +3344,7 @@ import {
3292
3344
  writeFileSync as writeFileSync3
3293
3345
  } from "node:fs";
3294
3346
  import { homedir as homedir3 } from "node:os";
3295
- import { dirname, join as join5 } from "node:path";
3347
+ import { dirname as dirname2, join as join5 } from "node:path";
3296
3348
  init_config();
3297
3349
 
3298
3350
  // src/hmy-config.ts
@@ -3439,7 +3491,7 @@ function stripSkillPreamble(content) {
3439
3491
  `;
3440
3492
  }
3441
3493
  function atomicWrite(filePath, content) {
3442
- const dir = dirname(filePath);
3494
+ const dir = dirname2(filePath);
3443
3495
  if (!existsSync4(dir))
3444
3496
  mkdirSync3(dir, { recursive: true });
3445
3497
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
@@ -3530,10 +3582,10 @@ async function refreshSkills(opts = {}) {
3530
3582
  continue;
3531
3583
  let siblingPath;
3532
3584
  if (samplePath.endsWith("SKILL.md")) {
3533
- const parentDir = dirname(dirname(samplePath));
3585
+ const parentDir = dirname2(dirname2(samplePath));
3534
3586
  siblingPath = `${parentDir}/${name}/SKILL.md`;
3535
3587
  } else {
3536
- const parentDir = dirname(samplePath);
3588
+ const parentDir = dirname2(samplePath);
3537
3589
  siblingPath = `${parentDir}/${name}.md`;
3538
3590
  }
3539
3591
  if (existsSync4(siblingPath)) {
@@ -4581,11 +4633,15 @@ var TOOLS = {
4581
4633
  }
4582
4634
  },
4583
4635
  harmony_set_project_context: {
4584
- description: "Set the active project context for subsequent operations",
4636
+ 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
4637
  inputSchema: {
4586
4638
  type: "object",
4587
4639
  properties: {
4588
- projectId: { type: "string" }
4640
+ projectId: { type: "string" },
4641
+ workspaceId: {
4642
+ type: "string",
4643
+ description: "The workspace this project belongs to. Optional — looked up when omitted."
4644
+ }
4589
4645
  },
4590
4646
  required: ["projectId"]
4591
4647
  }
@@ -5389,13 +5445,26 @@ var TOOLS = {
5389
5445
  type: "array",
5390
5446
  description: "The playbook's ordered stage objects.",
5391
5447
  items: { type: "object" }
5448
+ },
5449
+ triggerType: {
5450
+ type: "string",
5451
+ enum: ["manual", "auto"],
5452
+ description: "'manual' (default) — the playbook is applied by a person. 'auto' — it claims matching cards itself, and requires autoBind."
5453
+ },
5454
+ autoBind: {
5455
+ type: "object",
5456
+ 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."
5457
+ },
5458
+ catalogId: {
5459
+ type: "string",
5460
+ description: "Slug of the built-in template this came from (provenance only; never used to match)."
5392
5461
  }
5393
5462
  },
5394
5463
  required: ["name"]
5395
5464
  }
5396
5465
  },
5397
5466
  harmony_update_playbook: {
5398
- description: "Update a playbook's name, description, steps/stages, enabled flag, or lifecycle state ('active'|'deprecated').",
5467
+ description: "Update a playbook's name, description, steps/stages, enabled flag, lifecycle state ('active'|'deprecated'), or its auto-bind rule and arming.",
5399
5468
  inputSchema: {
5400
5469
  type: "object",
5401
5470
  properties: {
@@ -5418,6 +5487,15 @@ var TOOLS = {
5418
5487
  type: "string",
5419
5488
  enum: ["active", "deprecated"],
5420
5489
  description: "Lifecycle state"
5490
+ },
5491
+ triggerType: {
5492
+ type: "string",
5493
+ enum: ["manual", "auto"],
5494
+ description: "Arm ('auto') or disarm ('manual') automatic application. Arming requires a rule to be present or supplied in the same call."
5495
+ },
5496
+ autoBind: {
5497
+ type: "object",
5498
+ description: "Replace the auto-bind rule: {priority?, mode?: 'all'|'any', when: [{path, op, value}]}. Pass null to remove it."
5421
5499
  }
5422
5500
  },
5423
5501
  required: ["playbookId"]
@@ -5882,7 +5960,10 @@ async function handleToolCall(name, args, deps) {
5882
5960
  const where = resolved.project.workspaceName ? `project "${resolved.project.name ?? resolved.project.id}" (workspace "${resolved.project.workspaceName}")` : `project "${resolved.project.name ?? resolved.project.id}"`;
5883
5961
  const established = activeProjectId == null;
5884
5962
  if (established) {
5885
- deps.setActiveProject(resolved.project.id);
5963
+ deps.setActiveContext({
5964
+ projectId: resolved.project.id,
5965
+ workspaceId: resolved.project.workspaceId
5966
+ });
5886
5967
  }
5887
5968
  return {
5888
5969
  success: true,
@@ -6321,15 +6402,57 @@ ${options}
6321
6402
  }
6322
6403
  case "harmony_set_project_context": {
6323
6404
  const projectId = z.string().uuid().parse(args.projectId);
6324
- deps.setActiveProject(projectId);
6325
- return { success: true, activeProjectId: projectId };
6405
+ const explicitWorkspaceId = args.workspaceId ? z.string().uuid().parse(args.workspaceId) : null;
6406
+ let owningWorkspaceId = explicitWorkspaceId;
6407
+ if (!owningWorkspaceId) {
6408
+ try {
6409
+ const { workspaces } = await client3.listWorkspaces();
6410
+ for (const workspace of workspaces) {
6411
+ if (!workspace?.id)
6412
+ continue;
6413
+ const { projects } = await client3.listProjects(workspace.id);
6414
+ if (projects.some((p) => p?.id === projectId)) {
6415
+ owningWorkspaceId = workspace.id;
6416
+ break;
6417
+ }
6418
+ }
6419
+ } catch (error) {
6420
+ const reason = error instanceof Error ? error.message : String(error);
6421
+ return {
6422
+ success: false,
6423
+ activeProjectId: deps.getActiveProjectId(),
6424
+ activeWorkspaceId: deps.getActiveWorkspaceId(),
6425
+ 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.`
6426
+ };
6427
+ }
6428
+ if (!owningWorkspaceId) {
6429
+ return {
6430
+ success: false,
6431
+ activeProjectId: deps.getActiveProjectId(),
6432
+ activeWorkspaceId: deps.getActiveWorkspaceId(),
6433
+ 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.`
6434
+ };
6435
+ }
6436
+ }
6437
+ deps.setActiveContext({ projectId, workspaceId: owningWorkspaceId });
6438
+ return {
6439
+ success: true,
6440
+ activeProjectId: projectId,
6441
+ activeWorkspaceId: owningWorkspaceId
6442
+ };
6326
6443
  }
6327
6444
  case "harmony_get_context": {
6445
+ const report = describeActiveContext({
6446
+ projectId: deps.getActiveProjectId(),
6447
+ workspaceId: deps.getActiveWorkspaceId()
6448
+ });
6328
6449
  return {
6329
6450
  success: true,
6330
6451
  context: {
6331
- activeWorkspaceId: deps.getActiveWorkspaceId(),
6332
- activeProjectId: deps.getActiveProjectId()
6452
+ activeWorkspaceId: report.workspaceId,
6453
+ activeProjectId: report.projectId,
6454
+ consistent: report.consistent,
6455
+ ...report.note ? { note: report.note } : {}
6333
6456
  }
6334
6457
  };
6335
6458
  }
@@ -7178,7 +7301,10 @@ ${options}
7178
7301
  workspaceId,
7179
7302
  name: name2,
7180
7303
  description: args.description,
7181
- steps: args.steps
7304
+ steps: args.steps,
7305
+ triggerType: args.triggerType,
7306
+ autoBind: args.autoBind,
7307
+ catalogId: args.catalogId
7182
7308
  });
7183
7309
  return { success: true, playbook: result.playbook };
7184
7310
  }
@@ -7189,7 +7315,9 @@ ${options}
7189
7315
  description: args.description,
7190
7316
  steps: args.steps,
7191
7317
  enabled: args.enabled,
7192
- state: args.state
7318
+ state: args.state,
7319
+ triggerType: args.triggerType,
7320
+ ..."autoBind" in args ? { autoBind: args.autoBind } : {}
7193
7321
  });
7194
7322
  return { success: true, playbook: result.playbook };
7195
7323
  }
@@ -7266,8 +7394,10 @@ ${options}
7266
7394
  apiUrl: deps.getApiUrl()
7267
7395
  });
7268
7396
  deps.saveConfig({ apiKey: result.apiKey.rawKey });
7269
- deps.setActiveWorkspace(result.workspace.id);
7270
- deps.setActiveProject(result.project.id);
7397
+ deps.setActiveContext({
7398
+ projectId: result.project.id,
7399
+ workspaceId: result.workspace.id
7400
+ });
7271
7401
  deps.resetClient();
7272
7402
  return {
7273
7403
  success: true,
@@ -7289,7 +7419,7 @@ function createConfigDeps() {
7289
7419
  isConfigured,
7290
7420
  getActiveProjectId: () => getActiveProjectId(),
7291
7421
  getActiveWorkspaceId: () => getActiveWorkspaceId(),
7292
- setActiveProject: (id) => setActiveProject(id),
7422
+ setActiveContext: (context) => setActiveContext(context),
7293
7423
  setActiveWorkspace: (id) => setActiveWorkspace(id),
7294
7424
  getApiUrl,
7295
7425
  getMemoryDir: () => getMemoryDir(),
@@ -17,7 +17,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  // src/config.ts
18
18
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
19
  import { homedir } from "node:os";
20
- import { join } from "node:path";
20
+ import { dirname, join, parse, resolve } from "node:path";
21
21
  function getConfigDir() {
22
22
  return join(homedir(), ".harmony-mcp");
23
23
  }
@@ -27,6 +27,22 @@ function getConfigPath() {
27
27
  function getLocalConfigPath(cwd) {
28
28
  return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
29
29
  }
30
+ function findLocalConfigPath(cwd) {
31
+ const home = resolve(homedir());
32
+ let dir = resolve(cwd || process.cwd());
33
+ const { root } = parse(dir);
34
+ for (;; ) {
35
+ if (dir !== home && dir !== root) {
36
+ const candidate = join(dir, LOCAL_CONFIG_FILENAME);
37
+ if (existsSync(candidate))
38
+ return candidate;
39
+ }
40
+ const parent = dirname(dir);
41
+ if (parent === dir)
42
+ return null;
43
+ dir = parent;
44
+ }
45
+ }
30
46
  function emptyConfig() {
31
47
  return {
32
48
  apiKey: null,
@@ -78,8 +94,8 @@ function saveConfig(config) {
78
94
  });
79
95
  }
80
96
  function loadLocalConfig(cwd) {
81
- const localConfigPath = getLocalConfigPath(cwd);
82
- if (!existsSync(localConfigPath)) {
97
+ const localConfigPath = findLocalConfigPath(cwd);
98
+ if (localConfigPath === null) {
83
99
  return null;
84
100
  }
85
101
  try {
@@ -94,7 +110,7 @@ function loadLocalConfig(cwd) {
94
110
  }
95
111
  }
96
112
  function saveLocalConfig(config, cwd) {
97
- const localConfigPath = getLocalConfigPath(cwd);
113
+ const localConfigPath = findLocalConfigPath(cwd) ?? getLocalConfigPath(cwd);
98
114
  const existingConfig = loadLocalConfig(cwd) || {
99
115
  workspaceId: null,
100
116
  projectId: null
@@ -108,7 +124,7 @@ function saveLocalConfig(config, cwd) {
108
124
  writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
109
125
  }
110
126
  function hasLocalConfig(cwd) {
111
- return existsSync(getLocalConfigPath(cwd));
127
+ return findLocalConfigPath(cwd) !== null;
112
128
  }
113
129
  function getActiveCredential() {
114
130
  const config = loadConfig();
@@ -133,33 +149,69 @@ function getUserEmail() {
133
149
  function setUserEmail(email) {
134
150
  saveConfig({ userEmail: email });
135
151
  }
136
- function setActiveWorkspace(workspaceId, options) {
137
- if (options?.local) {
138
- saveLocalConfig({ workspaceId }, options.cwd);
139
- } else {
140
- saveConfig({ activeWorkspaceId: workspaceId });
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
- function setActiveProject(projectId, options) {
144
- if (options?.local) {
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({ activeProjectId: projectId });
164
+ saveConfig({
165
+ activeWorkspaceId: context.workspaceId,
166
+ activeProjectId: context.projectId
167
+ });
148
168
  }
149
169
  }
150
- function getActiveWorkspaceId(cwd) {
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?.workspaceId) {
153
- return localConfig.workspaceId;
180
+ if (localConfig) {
181
+ return {
182
+ workspaceId: localConfig.workspaceId ?? null,
183
+ projectId: localConfig.projectId ?? null
184
+ };
154
185
  }
155
- return loadConfig().activeWorkspaceId;
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
- const localConfig = loadLocalConfig(cwd);
159
- if (localConfig?.projectId) {
160
- return localConfig.projectId;
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 loadConfig().activeProjectId;
214
+ return { projectId, workspaceId, consistent: true, note: null };
163
215
  }
164
216
  function isConfigured() {
165
217
  const config = loadConfig();
@@ -238,7 +290,7 @@ function lockPath() {
238
290
  return join2(getConfigDir(), LOCK_FILENAME);
239
291
  }
240
292
  function sleep(ms) {
241
- return new Promise((resolve) => setTimeout(resolve, ms));
293
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
242
294
  }
243
295
  async function withRefreshLock(fn) {
244
296
  const path = lockPath();
@@ -993,7 +1045,7 @@ function getRetryDelay(attempt) {
993
1045
  const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
994
1046
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
995
1047
  }
996
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1048
+ var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
997
1049
 
998
1050
  class Semaphore {
999
1051
  permits;
@@ -1006,7 +1058,7 @@ class Semaphore {
1006
1058
  this.permits--;
1007
1059
  return;
1008
1060
  }
1009
- return new Promise((resolve) => this.queue.push(resolve));
1061
+ return new Promise((resolve2) => this.queue.push(resolve2));
1010
1062
  }
1011
1063
  release() {
1012
1064
  const next = this.queue.shift();