@codazen/harmonica-mcp 5.1.0 → 5.3.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 +363 -71
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21287,6 +21287,23 @@ watchPullRequest:
|
|
|
21287
21287
|
|
|
21288
21288
|
Re-read the policy before each step rather than caching it, so a human revoking
|
|
21289
21289
|
a toggle mid-flight takes effect on your next action.`;
|
|
21290
|
+
var WORK_ITEM_LIFECYCLE_INSTRUCTIONS = `WORK ITEM LIFECYCLE (CHANNEL-INDEPENDENT)
|
|
21291
|
+
|
|
21292
|
+
When you perform substantive work for a known Work Item, keep that Work Item's
|
|
21293
|
+
status accurate even when the work happens outside a Harmonica Session.
|
|
21294
|
+
|
|
21295
|
+
- Before starting, get the Work Item and transition it from \`not_started\` to
|
|
21296
|
+
\`in_progress\` with \`transition_work_item_status\`. Resume a \`blocked\` item
|
|
21297
|
+
only when its blocker has cleared. Do not reopen a \`done\` item unless new
|
|
21298
|
+
work genuinely belongs to that same item.
|
|
21299
|
+
- When its requested outcome is actually complete, transition it to \`done\`.
|
|
21300
|
+
If work cannot proceed, transition it to \`blocked\`. A conversation, task,
|
|
21301
|
+
or Session ending is not evidence that every associated Work Item is done.
|
|
21302
|
+
- Manage multiple Work Items independently. Leave untouched items unchanged;
|
|
21303
|
+
completing or blocking one must not change the others. A Session association
|
|
21304
|
+
is useful lineage, but it is never required in order to update status.
|
|
21305
|
+
- If the status tool is unavailable or fails, say plainly that the Work Item
|
|
21306
|
+
status was not updated rather than implying that it was.`;
|
|
21290
21307
|
async function createMcpServer(ctx, client, registerTools, registerResources) {
|
|
21291
21308
|
let guidelines;
|
|
21292
21309
|
try {
|
|
@@ -21294,7 +21311,11 @@ async function createMcpServer(ctx, client, registerTools, registerResources) {
|
|
|
21294
21311
|
} catch (err) {
|
|
21295
21312
|
console.warn("[MCP] Failed to load beat guidelines for server instructions:", err);
|
|
21296
21313
|
}
|
|
21297
|
-
const instructions = [
|
|
21314
|
+
const instructions = [
|
|
21315
|
+
guidelines,
|
|
21316
|
+
DELIVERY_LATITUDE_INSTRUCTIONS,
|
|
21317
|
+
WORK_ITEM_LIFECYCLE_INSTRUCTIONS
|
|
21318
|
+
].filter(Boolean).join("\n\n");
|
|
21298
21319
|
const server = new McpServer({
|
|
21299
21320
|
name: "harmonica",
|
|
21300
21321
|
version: "0.1.0"
|
|
@@ -22429,9 +22450,9 @@ var CHECK_MAX_SCORES = {
|
|
|
22429
22450
|
var BeatQualityGateError = class extends Error {
|
|
22430
22451
|
code = "beat_quality_gate_failed";
|
|
22431
22452
|
failure;
|
|
22432
|
-
constructor(
|
|
22433
|
-
super(
|
|
22434
|
-
this.failure =
|
|
22453
|
+
constructor(failure2) {
|
|
22454
|
+
super(failure2.message);
|
|
22455
|
+
this.failure = failure2;
|
|
22435
22456
|
}
|
|
22436
22457
|
};
|
|
22437
22458
|
var PlanVersionsIdempotencyError = class extends Error {
|
|
@@ -22480,20 +22501,30 @@ var POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
22480
22501
|
async function pollForCheck(client, taskId) {
|
|
22481
22502
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
22482
22503
|
let consecutiveErrors = 0;
|
|
22504
|
+
let consecutiveMissing = 0;
|
|
22483
22505
|
while (Date.now() < deadline) {
|
|
22484
22506
|
const [task, err] = await client.getTask(taskId).then(
|
|
22485
22507
|
(t) => [t, null],
|
|
22486
22508
|
(e) => [null, e]
|
|
22487
22509
|
);
|
|
22488
22510
|
if (err) {
|
|
22511
|
+
consecutiveMissing = 0;
|
|
22489
22512
|
consecutiveErrors++;
|
|
22490
22513
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22491
22514
|
throw new Error(`getTask failed ${consecutiveErrors} consecutive times: ${err instanceof Error ? err.message : String(err)}`);
|
|
22492
22515
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22493
22516
|
continue;
|
|
22494
22517
|
}
|
|
22518
|
+
if (!task) {
|
|
22519
|
+
consecutiveErrors = 0;
|
|
22520
|
+
consecutiveMissing++;
|
|
22521
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22522
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
22523
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22524
|
+
continue;
|
|
22525
|
+
}
|
|
22495
22526
|
consecutiveErrors = 0;
|
|
22496
|
-
|
|
22527
|
+
consecutiveMissing = 0;
|
|
22497
22528
|
if (task.status === "completed") {
|
|
22498
22529
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
22499
22530
|
return task.result;
|
|
@@ -22801,6 +22832,18 @@ ${revision.description}`);
|
|
|
22801
22832
|
}
|
|
22802
22833
|
return sections.join("\n");
|
|
22803
22834
|
}
|
|
22835
|
+
function formatRevisionPreview(revisionId, preview) {
|
|
22836
|
+
if (!preview) return `No preview recorded for revision: ${revisionId}`;
|
|
22837
|
+
const freshness = preview.isCurrent ? "Current \u2014 built from the head commit." : `Stale \u2014 built from ${preview.commitSha?.slice(0, 7) ?? "an unknown commit"}, which is no longer the head. Still opens.`;
|
|
22838
|
+
return [
|
|
22839
|
+
`# Preview: ${revisionId}`,
|
|
22840
|
+
`**URL:** ${preview.url}`,
|
|
22841
|
+
`**Built from:** ${preview.commitSha}`,
|
|
22842
|
+
`**Recorded:** ${preview.recordedAt}`,
|
|
22843
|
+
`
|
|
22844
|
+
${freshness}`
|
|
22845
|
+
].join("\n");
|
|
22846
|
+
}
|
|
22804
22847
|
|
|
22805
22848
|
// ../../libs/harmonica-services/src/mcp/formatters/note-formatter.ts
|
|
22806
22849
|
var TYPE_LABELS = {
|
|
@@ -23309,6 +23352,26 @@ ${formatDeliveryPolicy(
|
|
|
23309
23352
|
}
|
|
23310
23353
|
}
|
|
23311
23354
|
);
|
|
23355
|
+
server.tool(
|
|
23356
|
+
"get_revision_preview",
|
|
23357
|
+
"Get the preview link recorded for a Revision \u2014 the running environment its own pipeline published, and whether that build is still the pull request's head. A Revision with no recorded preview is reported plainly, not as an error: one whose change artifact is a document never gets one. A stale link still opens; it was built before the latest commit.",
|
|
23358
|
+
{
|
|
23359
|
+
revisionId: external_exports.string().describe("The revision ID (e.g., rev-abc123)")
|
|
23360
|
+
},
|
|
23361
|
+
async ({ revisionId }) => {
|
|
23362
|
+
try {
|
|
23363
|
+
const revision = await client.getRevision(revisionId);
|
|
23364
|
+
if (!revision) {
|
|
23365
|
+
return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
|
|
23366
|
+
}
|
|
23367
|
+
const preview = await client.resolveRevisionPreview(revisionId);
|
|
23368
|
+
return { content: [{ type: "text", text: formatRevisionPreview(revisionId, preview) }] };
|
|
23369
|
+
} catch (err) {
|
|
23370
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23371
|
+
return { content: [{ type: "text", text: `Failed to get revision preview: ${message}` }], isError: true };
|
|
23372
|
+
}
|
|
23373
|
+
}
|
|
23374
|
+
);
|
|
23312
23375
|
}
|
|
23313
23376
|
async function logBeatActivity(client, params) {
|
|
23314
23377
|
try {
|
|
@@ -24227,12 +24290,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS2 = 3;
|
|
|
24227
24290
|
async function pollForCheck2(client, taskId) {
|
|
24228
24291
|
const deadline = Date.now() + POLL_TIMEOUT_MS2;
|
|
24229
24292
|
let consecutiveErrors = 0;
|
|
24293
|
+
let consecutiveMissing = 0;
|
|
24230
24294
|
while (Date.now() < deadline) {
|
|
24231
24295
|
const [task, err] = await client.getTask(taskId).then(
|
|
24232
24296
|
(t) => [t, null],
|
|
24233
24297
|
(e) => [null, e]
|
|
24234
24298
|
);
|
|
24235
24299
|
if (err) {
|
|
24300
|
+
consecutiveMissing = 0;
|
|
24236
24301
|
consecutiveErrors++;
|
|
24237
24302
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS2) {
|
|
24238
24303
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24240,8 +24305,16 @@ async function pollForCheck2(client, taskId) {
|
|
|
24240
24305
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24241
24306
|
continue;
|
|
24242
24307
|
}
|
|
24308
|
+
if (!task) {
|
|
24309
|
+
consecutiveErrors = 0;
|
|
24310
|
+
consecutiveMissing++;
|
|
24311
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS2)
|
|
24312
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
24313
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24314
|
+
continue;
|
|
24315
|
+
}
|
|
24243
24316
|
consecutiveErrors = 0;
|
|
24244
|
-
|
|
24317
|
+
consecutiveMissing = 0;
|
|
24245
24318
|
if (task.status === "completed") {
|
|
24246
24319
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
24247
24320
|
return task.result;
|
|
@@ -25344,6 +25417,14 @@ var RevisionScmSchema = external_exports.object({
|
|
|
25344
25417
|
state: external_exports.enum(["draft", "open", "merged", "closed"]),
|
|
25345
25418
|
mergeSha: external_exports.string().optional()
|
|
25346
25419
|
});
|
|
25420
|
+
var RevisionPreviewSchema = external_exports.object({
|
|
25421
|
+
url: external_exports.string(),
|
|
25422
|
+
commitSha: external_exports.string(),
|
|
25423
|
+
recordedAt: external_exports.string()
|
|
25424
|
+
});
|
|
25425
|
+
var RevisionPreviewApiSchema = RevisionPreviewSchema.extend({
|
|
25426
|
+
isCurrent: external_exports.boolean()
|
|
25427
|
+
});
|
|
25347
25428
|
var RevisionApiSchema = external_exports.object({
|
|
25348
25429
|
revisionId: external_exports.string(),
|
|
25349
25430
|
beatId: external_exports.string(),
|
|
@@ -25686,6 +25767,7 @@ var NotificationSchema2 = external_exports.object({
|
|
|
25686
25767
|
systemId: external_exports.string(),
|
|
25687
25768
|
beatId: external_exports.string().optional(),
|
|
25688
25769
|
revisionId: external_exports.string().optional(),
|
|
25770
|
+
workItemIds: external_exports.array(external_exports.string()).optional(),
|
|
25689
25771
|
noteId: external_exports.string().optional(),
|
|
25690
25772
|
checkId: external_exports.string().optional(),
|
|
25691
25773
|
actorType: external_exports.enum(["user", "system"]),
|
|
@@ -25881,6 +25963,7 @@ var StartSessionRequestBodySchema = external_exports.object({
|
|
|
25881
25963
|
systemId: external_exports.string().min(1),
|
|
25882
25964
|
message: external_exports.string().min(1),
|
|
25883
25965
|
revisionId: external_exports.string().min(1).optional(),
|
|
25966
|
+
workItemIds: external_exports.array(external_exports.string().min(1)).max(50).optional(),
|
|
25884
25967
|
/** Caller's own Session id — nests the new Session under it (B-231). */
|
|
25885
25968
|
parentSessionId: external_exports.string().min(1).optional(),
|
|
25886
25969
|
enableWebSearch: external_exports.boolean().optional()
|
|
@@ -25950,7 +26033,8 @@ var WORK_ITEM_EFFORT_SIZES = [
|
|
|
25950
26033
|
"S",
|
|
25951
26034
|
"M",
|
|
25952
26035
|
"L",
|
|
25953
|
-
"XL"
|
|
26036
|
+
"XL",
|
|
26037
|
+
"XXL"
|
|
25954
26038
|
];
|
|
25955
26039
|
var WorkItemEffortSizeSchema = external_exports.enum(WORK_ITEM_EFFORT_SIZES);
|
|
25956
26040
|
var HumanAssigneeApiSchema = external_exports.object({
|
|
@@ -26090,9 +26174,24 @@ var WORK_ORDER_SIZES = [
|
|
|
26090
26174
|
"S",
|
|
26091
26175
|
"M",
|
|
26092
26176
|
"L",
|
|
26093
|
-
"XL"
|
|
26177
|
+
"XL",
|
|
26178
|
+
"XXL"
|
|
26094
26179
|
];
|
|
26095
26180
|
var WorkOrderSizeApiSchema = external_exports.enum(WORK_ORDER_SIZES);
|
|
26181
|
+
var WORK_ORDER_SIZE_VALUE_UNITS = {
|
|
26182
|
+
XS: 4,
|
|
26183
|
+
// half a person-day
|
|
26184
|
+
S: 8,
|
|
26185
|
+
// one person-day
|
|
26186
|
+
M: 24,
|
|
26187
|
+
// three person-days
|
|
26188
|
+
L: 40,
|
|
26189
|
+
// one person-week
|
|
26190
|
+
XL: 80,
|
|
26191
|
+
// two person-weeks
|
|
26192
|
+
XXL: 160
|
|
26193
|
+
// four person-weeks
|
|
26194
|
+
};
|
|
26096
26195
|
var WORK_REQUEST_ORIGINS = [
|
|
26097
26196
|
"client",
|
|
26098
26197
|
"internal"
|
|
@@ -26135,6 +26234,9 @@ var WorkOrderApiSchema = external_exports.object({
|
|
|
26135
26234
|
needsDecisionReason: NeedsDecisionReasonApiSchema.optional(),
|
|
26136
26235
|
/** Absent when the Order arrived before it was sized, which the composition counts. */
|
|
26137
26236
|
size: WorkOrderSizeApiSchema.optional(),
|
|
26237
|
+
archived: external_exports.boolean().optional(),
|
|
26238
|
+
archivedAt: external_exports.string().optional(),
|
|
26239
|
+
archivedReason: external_exports.string().optional(),
|
|
26138
26240
|
createdBy: external_exports.string().optional(),
|
|
26139
26241
|
version: external_exports.number().optional(),
|
|
26140
26242
|
createdAt: external_exports.string(),
|
|
@@ -26185,6 +26287,30 @@ var AccountApiSchema = external_exports.object({
|
|
|
26185
26287
|
updatedAt: external_exports.string()
|
|
26186
26288
|
}).passthrough();
|
|
26187
26289
|
var AccountsResponseSchema = external_exports.object({ accounts: external_exports.array(AccountApiSchema) }).passthrough();
|
|
26290
|
+
var ManagedRelationshipStatusApiSchema = external_exports.enum(["active", "suspended", "revoked"]);
|
|
26291
|
+
var ManagedRelationshipApiSchema = external_exports.object({
|
|
26292
|
+
relationshipId: external_exports.string(),
|
|
26293
|
+
agencyOrgId: external_exports.string(),
|
|
26294
|
+
customerOrgId: external_exports.string(),
|
|
26295
|
+
agencyAccountId: external_exports.string(),
|
|
26296
|
+
accountIds: external_exports.array(external_exports.string()),
|
|
26297
|
+
status: ManagedRelationshipStatusApiSchema,
|
|
26298
|
+
createdBy: external_exports.string(),
|
|
26299
|
+
statusChangedBy: external_exports.string().nullish(),
|
|
26300
|
+
statusChangedAt: external_exports.string().nullish(),
|
|
26301
|
+
suspendedBy: external_exports.string().nullish(),
|
|
26302
|
+
suspendedAt: external_exports.string().nullish(),
|
|
26303
|
+
reactivatedBy: external_exports.string().nullish(),
|
|
26304
|
+
reactivatedAt: external_exports.string().nullish(),
|
|
26305
|
+
revokedBy: external_exports.string().nullish(),
|
|
26306
|
+
revokedAt: external_exports.string().nullish(),
|
|
26307
|
+
version: external_exports.number(),
|
|
26308
|
+
createdAt: external_exports.string(),
|
|
26309
|
+
updatedAt: external_exports.string()
|
|
26310
|
+
}).passthrough();
|
|
26311
|
+
var ManagedRelationshipsResponseSchema = external_exports.object({
|
|
26312
|
+
relationships: external_exports.array(ManagedRelationshipApiSchema)
|
|
26313
|
+
}).passthrough();
|
|
26188
26314
|
var AccountSystemApiSchema = external_exports.object({
|
|
26189
26315
|
systemId: external_exports.string(),
|
|
26190
26316
|
accountId: external_exports.string(),
|
|
@@ -26922,6 +27048,100 @@ ${formatLayerDetail(layer)}` }] };
|
|
|
26922
27048
|
);
|
|
26923
27049
|
}
|
|
26924
27050
|
|
|
27051
|
+
// ../../libs/harmonica-services/src/mcp/tools/managed-relationship-tools.ts
|
|
27052
|
+
function formatRelationship(relationship) {
|
|
27053
|
+
if (!relationship) return "Managed relationship not found.";
|
|
27054
|
+
return [
|
|
27055
|
+
`ID: ${relationship.relationshipId}`,
|
|
27056
|
+
`Agency Organization: ${relationship.agencyOrgId}`,
|
|
27057
|
+
`Customer Organization: ${relationship.customerOrgId}`,
|
|
27058
|
+
`Agency Account: ${relationship.agencyAccountId}`,
|
|
27059
|
+
`Customer Accounts: ${relationship.accountIds.join(", ")}`,
|
|
27060
|
+
`Status: ${relationship.status}`
|
|
27061
|
+
].join("\n");
|
|
27062
|
+
}
|
|
27063
|
+
function failure(action, error2) {
|
|
27064
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
27065
|
+
return {
|
|
27066
|
+
content: [{ type: "text", text: `Failed to ${action}: ${message}` }],
|
|
27067
|
+
isError: true
|
|
27068
|
+
};
|
|
27069
|
+
}
|
|
27070
|
+
function registerManagedRelationshipTools(server, _ctx, client) {
|
|
27071
|
+
server.tool(
|
|
27072
|
+
"create_managed_relationship",
|
|
27073
|
+
"Connect an agency Organization to a separately owned customer Organization and grant the agency access only to the named customer Accounts. Both Organizations, the agency commercial Account, and every customer Account must already exist. Phase 1 is administrator-provisioned; no approval request is sent.",
|
|
27074
|
+
{
|
|
27075
|
+
agencyOrgId: external_exports.string().describe("The managing agency Organization ID"),
|
|
27076
|
+
customerOrgId: external_exports.string().describe("The customer-owned Organization ID"),
|
|
27077
|
+
agencyAccountId: external_exports.string().describe("The agency-owned commercial Account for this customer"),
|
|
27078
|
+
accountIds: external_exports.array(external_exports.string()).min(1).max(10).describe("Customer-owned Account IDs delegated to the agency")
|
|
27079
|
+
},
|
|
27080
|
+
async (input) => {
|
|
27081
|
+
try {
|
|
27082
|
+
const relationship = await client.createManagedRelationship(input);
|
|
27083
|
+
return { content: [{ type: "text", text: `Created managed relationship.
|
|
27084
|
+
${formatRelationship(relationship)}` }] };
|
|
27085
|
+
} catch (error2) {
|
|
27086
|
+
return failure("create managed relationship", error2);
|
|
27087
|
+
}
|
|
27088
|
+
}
|
|
27089
|
+
);
|
|
27090
|
+
server.tool(
|
|
27091
|
+
"list_managed_relationships",
|
|
27092
|
+
"List customer Organizations and Account scopes managed by an agency Organization.",
|
|
27093
|
+
{ agencyOrgId: external_exports.string().describe("The managing agency Organization ID") },
|
|
27094
|
+
async ({ agencyOrgId }) => {
|
|
27095
|
+
try {
|
|
27096
|
+
const relationships = await client.listManagedRelationships(agencyOrgId);
|
|
27097
|
+
const text = relationships.length === 0 ? "No managed customer organizations found." : relationships.map(formatRelationship).join("\n\n");
|
|
27098
|
+
return { content: [{ type: "text", text }] };
|
|
27099
|
+
} catch (error2) {
|
|
27100
|
+
return failure("list managed relationships", error2);
|
|
27101
|
+
}
|
|
27102
|
+
}
|
|
27103
|
+
);
|
|
27104
|
+
server.tool(
|
|
27105
|
+
"get_managed_relationship",
|
|
27106
|
+
"Get one managed relationship and its explicitly delegated customer Account IDs.",
|
|
27107
|
+
{ relationshipId: external_exports.string().describe("The managed relationship ID") },
|
|
27108
|
+
async ({ relationshipId }) => {
|
|
27109
|
+
try {
|
|
27110
|
+
return {
|
|
27111
|
+
content: [{
|
|
27112
|
+
type: "text",
|
|
27113
|
+
text: formatRelationship(await client.getManagedRelationship(relationshipId))
|
|
27114
|
+
}]
|
|
27115
|
+
};
|
|
27116
|
+
} catch (error2) {
|
|
27117
|
+
return failure("get managed relationship", error2);
|
|
27118
|
+
}
|
|
27119
|
+
}
|
|
27120
|
+
);
|
|
27121
|
+
server.tool(
|
|
27122
|
+
"set_managed_relationship_status",
|
|
27123
|
+
"Suspend, reactivate, or permanently revoke a managed relationship. Revoked relationships cannot be reactivated.",
|
|
27124
|
+
{
|
|
27125
|
+
relationshipId: external_exports.string().describe("The managed relationship ID"),
|
|
27126
|
+
status: external_exports.enum(["active", "suspended", "revoked"]).describe("The target status")
|
|
27127
|
+
},
|
|
27128
|
+
async ({ relationshipId, status }) => {
|
|
27129
|
+
try {
|
|
27130
|
+
const relationship = await client.setManagedRelationshipStatus(relationshipId, status);
|
|
27131
|
+
if (!relationship) {
|
|
27132
|
+
return {
|
|
27133
|
+
content: [{ type: "text", text: "Managed relationship not found." }],
|
|
27134
|
+
isError: true
|
|
27135
|
+
};
|
|
27136
|
+
}
|
|
27137
|
+
return { content: [{ type: "text", text: formatRelationship(relationship) }] };
|
|
27138
|
+
} catch (error2) {
|
|
27139
|
+
return failure("update managed relationship", error2);
|
|
27140
|
+
}
|
|
27141
|
+
}
|
|
27142
|
+
);
|
|
27143
|
+
}
|
|
27144
|
+
|
|
26925
27145
|
// ../../libs/harmonica-services/src/mcp/tools/membership-tools.ts
|
|
26926
27146
|
var orgIdArg = external_exports.string().describe("The organization ID \u2014 see list_organizations");
|
|
26927
27147
|
function registerMembershipTools(server, ctx, client) {
|
|
@@ -28292,22 +28512,16 @@ ${formatTree(tree.root)}` }] };
|
|
|
28292
28512
|
}
|
|
28293
28513
|
let scopeSystemId = systemId;
|
|
28294
28514
|
if (!scopeSystemId) {
|
|
28295
|
-
let bindings;
|
|
28296
28515
|
let bindingsFetchFailed = false;
|
|
28297
28516
|
try {
|
|
28298
|
-
bindings = await client.getNotebookScopeBindings(notebookId);
|
|
28517
|
+
const bindings = await client.getNotebookScopeBindings(notebookId);
|
|
28299
28518
|
scopeSystemId = bindings?.find((b) => b.scopeType === "system")?.scopeId;
|
|
28300
28519
|
} catch {
|
|
28301
28520
|
bindingsFetchFailed = true;
|
|
28302
28521
|
}
|
|
28303
28522
|
if (!scopeSystemId) {
|
|
28304
|
-
|
|
28305
|
-
|
|
28306
|
-
if (hasLegacyProjectBinding) {
|
|
28307
|
-
return { content: [{ type: "text", text: `Notebook "${notebookId}" has only a legacy 'project' scope binding \u2014 record may need B-247 migration re-check.` }], isError: true };
|
|
28308
|
-
}
|
|
28309
|
-
}
|
|
28310
|
-
return { content: [{ type: "text", text: "No system scope: pass systemId or bind the notebook to a system first." }], isError: true };
|
|
28523
|
+
const msg = bindingsFetchFailed ? "Failed to look up notebook scope binding \u2014 this may be transient. Pass an explicit systemId to proceed." : "No system scope: pass systemId or bind the notebook to a system first.";
|
|
28524
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
28311
28525
|
}
|
|
28312
28526
|
}
|
|
28313
28527
|
let tree;
|
|
@@ -28362,7 +28576,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28362
28576
|
);
|
|
28363
28577
|
server.tool(
|
|
28364
28578
|
"update_notebook_collection",
|
|
28365
|
-
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type,
|
|
28579
|
+
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type, system scope, limit, order, significance tier) or its heading, without removing and re-adding it. Only the fields you pass change; the rest keep their current values. Identify the section by its elementId (from get_notebook_tree) and the page it sits on.",
|
|
28366
28580
|
{
|
|
28367
28581
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28368
28582
|
elementId: external_exports.string().describe("The collection section's elementId (from get_notebook_tree)"),
|
|
@@ -28411,10 +28625,9 @@ ${formatTree(tree.root)}` }] };
|
|
|
28411
28625
|
return { content: [{ type: "text", text: `Element "${elementId}" is not a live collection \u2014 its configuration cannot be edited here.` }], isError: true };
|
|
28412
28626
|
}
|
|
28413
28627
|
const base = existing.query;
|
|
28414
|
-
const normalizedScope = systemId ? `system:${systemId}` : base.scope
|
|
28415
|
-
|
|
28416
|
-
|
|
28417
|
-
return { content: [{ type: "text", text: `Stored scope '${base.scope}' has no system id \u2014 pass an explicit systemId to re-bind this collection.` }], isError: true };
|
|
28628
|
+
const normalizedScope = systemId ? `system:${systemId}` : base.scope;
|
|
28629
|
+
if (!normalizedScope.startsWith("system:") || normalizedScope.length <= "system:".length) {
|
|
28630
|
+
return { content: [{ type: "text", text: `Stored scope '${base.scope}' is not a valid system scope \u2014 pass an explicit systemId to re-bind this collection.` }], isError: true };
|
|
28418
28631
|
}
|
|
28419
28632
|
const mergedQuery = {
|
|
28420
28633
|
source: "notes",
|
|
@@ -28503,9 +28716,9 @@ ${scopeText}` }] };
|
|
|
28503
28716
|
);
|
|
28504
28717
|
server.tool(
|
|
28505
28718
|
"list_notebooks",
|
|
28506
|
-
"List Notebooks reachable from a scope (org, teamspace, or
|
|
28719
|
+
"List Notebooks reachable from a scope (org, teamspace, system, or account). Returns all notebooks bound to that scope.",
|
|
28507
28720
|
{
|
|
28508
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, or
|
|
28721
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28509
28722
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity"),
|
|
28510
28723
|
includeArchived: external_exports.boolean().optional().describe("Include archived Notebooks in the results. Default: false (archived excluded).")
|
|
28511
28724
|
},
|
|
@@ -28566,10 +28779,10 @@ ${formatNotebook(updated)}` }] };
|
|
|
28566
28779
|
);
|
|
28567
28780
|
server.tool(
|
|
28568
28781
|
"add_notebook_shortcut",
|
|
28569
|
-
"Add a curated, read-only shortcut from a scope (org, teamspace,
|
|
28782
|
+
"Add a curated, read-only shortcut from a scope (org, teamspace, system, or account) to a Notebook owned elsewhere in the SAME organization. Unlike set_notebook_scopes, a shortcut grants no rename/archive/delete rights over the Notebook in the consuming scope \u2014 it is a link, not ownership.",
|
|
28570
28783
|
{
|
|
28571
28784
|
notebookId: external_exports.string().describe("The Notebook ID to shortcut"),
|
|
28572
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28785
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28573
28786
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity"),
|
|
28574
28787
|
createdBy: external_exports.string().optional().describe("Actor ID \u2014 defaults to the session user")
|
|
28575
28788
|
},
|
|
@@ -28598,7 +28811,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28598
28811
|
"Remove a Notebook shortcut from a scope. Only removes the shortcut reference \u2014 never affects the source Notebook or its real scope bindings.",
|
|
28599
28812
|
{
|
|
28600
28813
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28601
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28814
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28602
28815
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity")
|
|
28603
28816
|
},
|
|
28604
28817
|
async ({ notebookId, scopeType, scopeId }) => {
|
|
@@ -28621,7 +28834,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28621
28834
|
"list_notebook_shortcuts",
|
|
28622
28835
|
"List a scope\u2019s curated Notebook shortcuts \u2014 Notebooks owned elsewhere in the org that this scope has chosen to surface, hydrated with each source Notebook\u2019s live title/status.",
|
|
28623
28836
|
{
|
|
28624
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace,
|
|
28837
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28625
28838
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity")
|
|
28626
28839
|
},
|
|
28627
28840
|
async ({ scopeType, scopeId }) => {
|
|
@@ -28976,12 +29189,14 @@ var ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
28976
29189
|
async function pollForRootCodaCheck(client, taskId) {
|
|
28977
29190
|
const deadline = Date.now() + ROOT_CODA_POLL_TIMEOUT_MS;
|
|
28978
29191
|
let consecutiveErrors = 0;
|
|
29192
|
+
let consecutiveMissing = 0;
|
|
28979
29193
|
while (Date.now() < deadline) {
|
|
28980
29194
|
const [task, err] = await client.getTask(taskId).then(
|
|
28981
29195
|
(t) => [t, null],
|
|
28982
29196
|
(e) => [null, e]
|
|
28983
29197
|
);
|
|
28984
29198
|
if (err) {
|
|
29199
|
+
consecutiveMissing = 0;
|
|
28985
29200
|
consecutiveErrors++;
|
|
28986
29201
|
if (consecutiveErrors >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS) {
|
|
28987
29202
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -28989,8 +29204,16 @@ async function pollForRootCodaCheck(client, taskId) {
|
|
|
28989
29204
|
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28990
29205
|
continue;
|
|
28991
29206
|
}
|
|
29207
|
+
if (!task) {
|
|
29208
|
+
consecutiveErrors = 0;
|
|
29209
|
+
consecutiveMissing++;
|
|
29210
|
+
if (consecutiveMissing >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS)
|
|
29211
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29212
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
29213
|
+
continue;
|
|
29214
|
+
}
|
|
28992
29215
|
consecutiveErrors = 0;
|
|
28993
|
-
|
|
29216
|
+
consecutiveMissing = 0;
|
|
28994
29217
|
if (task.status === "completed") {
|
|
28995
29218
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
28996
29219
|
const result = task.result;
|
|
@@ -29333,12 +29556,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS4 = 3;
|
|
|
29333
29556
|
async function pollForCheck4(client, taskId) {
|
|
29334
29557
|
const deadline = Date.now() + POLL_TIMEOUT_MS4;
|
|
29335
29558
|
let consecutiveErrors = 0;
|
|
29559
|
+
let consecutiveMissing = 0;
|
|
29336
29560
|
while (Date.now() < deadline) {
|
|
29337
29561
|
const [task, err] = await client.getTask(taskId).then(
|
|
29338
29562
|
(t) => [t, null],
|
|
29339
29563
|
(e) => [null, e]
|
|
29340
29564
|
);
|
|
29341
29565
|
if (err) {
|
|
29566
|
+
consecutiveMissing = 0;
|
|
29342
29567
|
consecutiveErrors++;
|
|
29343
29568
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS4) {
|
|
29344
29569
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -29346,8 +29571,16 @@ async function pollForCheck4(client, taskId) {
|
|
|
29346
29571
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29347
29572
|
continue;
|
|
29348
29573
|
}
|
|
29574
|
+
if (!task) {
|
|
29575
|
+
consecutiveErrors = 0;
|
|
29576
|
+
consecutiveMissing++;
|
|
29577
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS4)
|
|
29578
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29579
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29580
|
+
continue;
|
|
29581
|
+
}
|
|
29349
29582
|
consecutiveErrors = 0;
|
|
29350
|
-
|
|
29583
|
+
consecutiveMissing = 0;
|
|
29351
29584
|
if (task.status === "completed") {
|
|
29352
29585
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
29353
29586
|
return task.result;
|
|
@@ -30684,19 +30917,16 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30684
30917
|
);
|
|
30685
30918
|
}
|
|
30686
30919
|
function normalizeSnapshot(raw) {
|
|
30687
|
-
const anyRaw = raw;
|
|
30688
30920
|
return {
|
|
30689
30921
|
...raw,
|
|
30690
|
-
|
|
30691
|
-
system: raw.system ?? anyRaw["project"],
|
|
30922
|
+
system: raw.system,
|
|
30692
30923
|
beats: raw.beats ?? [],
|
|
30693
30924
|
beatIndex: raw.beatIndex ?? [],
|
|
30694
30925
|
proposals: raw.proposals ?? [],
|
|
30695
30926
|
proposalIndex: raw.proposalIndex ?? [],
|
|
30696
30927
|
revisions: raw.revisions ?? [],
|
|
30697
30928
|
revisionIndex: raw.revisionIndex ?? [],
|
|
30698
|
-
|
|
30699
|
-
systemRevisionIndex: raw.systemRevisionIndex ?? anyRaw["projectRevisionIndex"] ?? [],
|
|
30929
|
+
systemRevisionIndex: raw.systemRevisionIndex ?? [],
|
|
30700
30930
|
activities: raw.activities ?? [],
|
|
30701
30931
|
activityIndex: raw.activityIndex ?? [],
|
|
30702
30932
|
notes: raw.notes ?? [],
|
|
@@ -30709,8 +30939,7 @@ function normalizeSnapshot(raw) {
|
|
|
30709
30939
|
promptLogs: raw.promptLogs ?? [],
|
|
30710
30940
|
beatVersions: raw.beatVersions ?? [],
|
|
30711
30941
|
beatBeatVersionIndex: raw.beatBeatVersionIndex ?? [],
|
|
30712
|
-
|
|
30713
|
-
systemBeatVersionIndex: raw.systemBeatVersionIndex ?? anyRaw["projectBeatVersionIndex"] ?? [],
|
|
30942
|
+
systemBeatVersionIndex: raw.systemBeatVersionIndex ?? [],
|
|
30714
30943
|
beatVersionDropAssignments: raw.beatVersionDropAssignments ?? [],
|
|
30715
30944
|
drops: raw.drops ?? [],
|
|
30716
30945
|
accountDropIndex: raw.accountDropIndex ?? [],
|
|
@@ -30806,8 +31035,11 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30806
31035
|
if (typeof obj["version"] !== "number" || obj["version"] <= 0) {
|
|
30807
31036
|
throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
|
|
30808
31037
|
}
|
|
30809
|
-
const
|
|
30810
|
-
if (typeof
|
|
31038
|
+
const system = obj["system"];
|
|
31039
|
+
if (typeof system !== "object" || system === null || typeof system["systemId"] !== "string") {
|
|
31040
|
+
if (typeof obj["project"] === "object" && obj["project"] !== null) {
|
|
31041
|
+
throw new Error(`Snapshot${sourceHint} uses the v1-v3 'project' field format, which is no longer supported. Export a fresh snapshot from your environment and re-import.`);
|
|
31042
|
+
}
|
|
30811
31043
|
throw new Error(`Snapshot${sourceHint} is missing 'system.systemId' \u2014 it may not be a valid project snapshot.`);
|
|
30812
31044
|
}
|
|
30813
31045
|
}
|
|
@@ -31417,7 +31649,7 @@ ${t.acceptanceCriteria}` : null,
|
|
|
31417
31649
|
};
|
|
31418
31650
|
server.tool(
|
|
31419
31651
|
"create_track",
|
|
31420
|
-
'Create a Track under a Movement. Use for scoped initiatives \u2014 client SOWs, internal phases, engagement containers \u2014 that collect
|
|
31652
|
+
'Create a Track under a Movement. Use for scoped initiatives \u2014 client SOWs, internal phases, engagement containers \u2014 that collect work items and Drops. Do NOT use for software capabilities the product provides to end users (those are Beats). Recognition cues: "this engagement", "this SOW", "this phase of work", "the client project", "this track" \u2192 Track.',
|
|
31421
31653
|
createTrackSchema,
|
|
31422
31654
|
createTrackHandler
|
|
31423
31655
|
);
|
|
@@ -31857,24 +32089,47 @@ function registerValueVelocityTools(server, ctx, client) {
|
|
|
31857
32089
|
);
|
|
31858
32090
|
}
|
|
31859
32091
|
|
|
31860
|
-
// ../../libs/harmonica-services/src/
|
|
31861
|
-
var
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
|
|
32092
|
+
// ../../libs/harmonica-services/src/tool-schemas/sizing.ts
|
|
32093
|
+
var WORK_ORDER_SIZE_ENUM = [
|
|
32094
|
+
"XS",
|
|
32095
|
+
"S",
|
|
32096
|
+
"M",
|
|
32097
|
+
"L",
|
|
32098
|
+
"XL",
|
|
32099
|
+
"XXL"
|
|
32100
|
+
];
|
|
31865
32101
|
var WORK_ITEM_EFFORT_SIZE_ENUM = [
|
|
31866
32102
|
"XS",
|
|
31867
32103
|
"S",
|
|
31868
32104
|
"M",
|
|
31869
32105
|
"L",
|
|
31870
|
-
"XL"
|
|
32106
|
+
"XL",
|
|
32107
|
+
"XXL"
|
|
31871
32108
|
];
|
|
32109
|
+
var SIZE_LABELS = {
|
|
32110
|
+
XS: "half a person-day",
|
|
32111
|
+
S: "one person-day",
|
|
32112
|
+
M: "three person-days",
|
|
32113
|
+
L: "one person-week",
|
|
32114
|
+
XL: "two person-weeks",
|
|
32115
|
+
XXL: "four person-weeks"
|
|
32116
|
+
};
|
|
32117
|
+
var SIZE_TABLE = WORK_ORDER_SIZE_ENUM.map(
|
|
32118
|
+
(size) => `${size}=${SIZE_LABELS[size]} (${WORK_ORDER_SIZE_VALUE_UNITS[size]} VU)`
|
|
32119
|
+
).join(", ");
|
|
32120
|
+
var SIZE_BASIS = `Estimate the effort with NO AI assistance \u2014 the fully-human person-hours. Do NOT size what it will actually take with AI. Person-hours, not calendar time: two people for a week is XL, not L. ${SIZE_TABLE}.`;
|
|
32121
|
+
|
|
32122
|
+
// ../../libs/harmonica-services/src/mcp/tools/work-item-tools.ts
|
|
32123
|
+
var WORK_ITEM_STATUS_ENUM = ["not_started", "in_progress", "blocked", "done"];
|
|
32124
|
+
function formatOwner(owner) {
|
|
32125
|
+
return owner ? `${owner.name} <${owner.email}>` : "unassigned";
|
|
32126
|
+
}
|
|
31872
32127
|
function registerWorkItemTools(server, _ctx, client) {
|
|
31873
32128
|
server.tool(
|
|
31874
32129
|
"list_track_work_items",
|
|
31875
|
-
'List all work items under a Track
|
|
32130
|
+
'List all work items under a Track. A work item is an owned, statused unit of operational work. Recognition cues: "what work is on this track", "list the work items" \u2192 list_track_work_items.',
|
|
31876
32131
|
{
|
|
31877
|
-
trackId: external_exports.string().describe("The Track
|
|
32132
|
+
trackId: external_exports.string().describe("The Track ID")
|
|
31878
32133
|
},
|
|
31879
32134
|
async ({ trackId }) => {
|
|
31880
32135
|
const workItems = await client.listTrackWorkItems(trackId);
|
|
@@ -31920,15 +32175,15 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
31920
32175
|
);
|
|
31921
32176
|
server.tool(
|
|
31922
32177
|
"create_work_item",
|
|
31923
|
-
'Create a work item under a Track
|
|
32178
|
+
'Create a work item under a Track. A work item is an owned, statused unit of operational work \u2014 it is NOT a Beat. Starts in "not_started" status. Optionally link it to a Beat Version for capability lineage (v2, N-4E09-6574) \u2014 the link is one-directional and never required. Optionally link it to a Work Order it delivers part of; leaving it unattached is a supported state, not a gap to fill (N-4E09-7160). Optionally link it to a Bar it is planned into; leaving it unplanned is likewise a real state, not a gap (B-298).',
|
|
31924
32179
|
{
|
|
31925
|
-
trackId: external_exports.string().describe("The Track
|
|
32180
|
+
trackId: external_exports.string().describe("The Track ID this work item belongs to"),
|
|
31926
32181
|
title: external_exports.string().min(1).max(300).describe("Work item title"),
|
|
31927
32182
|
ownerName: external_exports.string().min(1).optional().describe("Optional owner display name (requires ownerEmail too)"),
|
|
31928
32183
|
ownerEmail: external_exports.string().email().optional().describe("Optional owner email (requires ownerName too). A work item may be left unowned."),
|
|
31929
32184
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("Optional committed estimate in hours"),
|
|
31930
|
-
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).optional().describe(
|
|
31931
|
-
valueUnits: external_exports.number().nonnegative().optional().describe(
|
|
32185
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).optional().describe(`Optional rough t-shirt effort size \u2014 seeds valueUnits. ${SIZE_BASIS}`),
|
|
32186
|
+
valueUnits: external_exports.number().nonnegative().optional().describe(`Optional Value Units \u2014 fully-human person-hours. Defaults from effortSize; pass only to override. ${SIZE_BASIS}`),
|
|
31932
32187
|
costPerValueUnit: external_exports.number().nonnegative().optional().describe("Optional price per Value Unit"),
|
|
31933
32188
|
createdBy: external_exports.string().optional().describe("Optional creator identity for audit"),
|
|
31934
32189
|
beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist."),
|
|
@@ -31976,7 +32231,9 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
31976
32231
|
unassignOwner: external_exports.boolean().optional().describe("Pass true to clear the owner, leaving the work item unowned. Cannot be combined with ownerName/ownerEmail."),
|
|
31977
32232
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("New committed estimate in hours"),
|
|
31978
32233
|
actualHours: external_exports.number().nonnegative().optional().describe("New actual hours logged"),
|
|
31979
|
-
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe(
|
|
32234
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe(
|
|
32235
|
+
`Rough t-shirt effort size. Pass null to clear; omit to leave untouched. ${SIZE_BASIS}`
|
|
32236
|
+
),
|
|
31980
32237
|
valueUnits: external_exports.number().nonnegative().nullable().optional().describe("Value Units (commercial lens). Pass null to clear; omit to leave untouched."),
|
|
31981
32238
|
costPerValueUnit: external_exports.number().nonnegative().nullable().optional().describe("Price per Value Unit. Pass null to clear; omit to leave untouched."),
|
|
31982
32239
|
beatVersionId: external_exports.string().min(1).nullable().optional().describe("Link to a Beat Version for capability lineage. Pass null to unlink; omit to leave the current link untouched."),
|
|
@@ -32037,10 +32294,10 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32037
32294
|
);
|
|
32038
32295
|
server.tool(
|
|
32039
32296
|
"move_work_item",
|
|
32040
|
-
"Move a work item to a different Track
|
|
32297
|
+
"Move a work item to a different Track. Re-derives its teamspace/org scope from the new Track.",
|
|
32041
32298
|
{
|
|
32042
32299
|
workItemId: external_exports.string().describe("The work item ID"),
|
|
32043
|
-
newTrackId: external_exports.string().describe("The destination Track
|
|
32300
|
+
newTrackId: external_exports.string().describe("The destination Track ID. Must exist.")
|
|
32044
32301
|
},
|
|
32045
32302
|
async ({ workItemId, newTrackId }) => {
|
|
32046
32303
|
const wi = await client.moveWorkItem(workItemId, newTrackId);
|
|
@@ -32083,13 +32340,12 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32083
32340
|
}
|
|
32084
32341
|
|
|
32085
32342
|
// ../../libs/harmonica-services/src/tool-schemas/work-order.ts
|
|
32086
|
-
var WORK_ORDER_SIZE_ENUM = ["XS", "S", "M", "L", "XL"];
|
|
32087
32343
|
var create_work_order = {
|
|
32088
32344
|
workRequestId: external_exports.string().min(1).describe("The Work Request this order was split from \u2014 immutable lineage"),
|
|
32089
32345
|
title: external_exports.string().min(1).max(300).describe("Work order title"),
|
|
32090
32346
|
description: external_exports.string().max(1e4).optional().describe("Optional detail beyond the title"),
|
|
32091
32347
|
size: external_exports.enum(WORK_ORDER_SIZE_ENUM).optional().describe(
|
|
32092
|
-
"Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them."
|
|
32348
|
+
"Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them. " + SIZE_BASIS
|
|
32093
32349
|
),
|
|
32094
32350
|
createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
|
|
32095
32351
|
};
|
|
@@ -32112,13 +32368,6 @@ var NEEDS_DECISION_REASON_ENUM = [
|
|
|
32112
32368
|
"exceeds_capacity",
|
|
32113
32369
|
"incoherent_with_decision"
|
|
32114
32370
|
];
|
|
32115
|
-
var WORK_ORDER_SIZE_ENUM2 = [
|
|
32116
|
-
"XS",
|
|
32117
|
-
"S",
|
|
32118
|
-
"M",
|
|
32119
|
-
"L",
|
|
32120
|
-
"XL"
|
|
32121
|
-
];
|
|
32122
32371
|
function oneLine(value) {
|
|
32123
32372
|
return value.replace(/[\r\n]+/g, " ");
|
|
32124
32373
|
}
|
|
@@ -32134,14 +32383,14 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32134
32383
|
// exactly the caller this alias exists to greet. Requiring it would answer
|
|
32135
32384
|
// the migrated caller with a schema error instead of the migration message.
|
|
32136
32385
|
{
|
|
32137
|
-
trackId: external_exports.string().optional().describe("Ignored \u2014 the Track
|
|
32386
|
+
trackId: external_exports.string().optional().describe("Ignored \u2014 the Track ID is no longer meaningful for Order scoping")
|
|
32138
32387
|
},
|
|
32139
32388
|
async () => {
|
|
32140
32389
|
return {
|
|
32141
32390
|
content: [
|
|
32142
32391
|
{
|
|
32143
32392
|
type: "text",
|
|
32144
|
-
text: "list_track_work_orders is deprecated. Use list_movement_work_orders(movementId) to fetch all Orders for an engagement \u2014 Orders are now scoped to their Movement, not their Track. If you have only a Track id, its Movement is on the Track itself:
|
|
32393
|
+
text: "list_track_work_orders is deprecated. Use list_movement_work_orders(movementId) to fetch all Orders for an engagement \u2014 Orders are now scoped to their Movement, not their Track. If you have only a Track id, its Movement is on the Track itself: get_track(trackId) returns movementId."
|
|
32145
32394
|
}
|
|
32146
32395
|
],
|
|
32147
32396
|
// The caller asked for a list and is getting none. A result without
|
|
@@ -32226,7 +32475,9 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32226
32475
|
workOrderId: external_exports.string().describe("The Work Order ID"),
|
|
32227
32476
|
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
32228
32477
|
description: external_exports.string().max(1e4).nullable().optional().describe("New description. Pass null to clear; omit to leave untouched."),
|
|
32229
|
-
size: external_exports.enum(
|
|
32478
|
+
size: external_exports.enum(WORK_ORDER_SIZE_ENUM).nullable().optional().describe(
|
|
32479
|
+
"New t-shirt size. Pass null to return it to unsized; omit to leave untouched. " + SIZE_BASIS
|
|
32480
|
+
)
|
|
32230
32481
|
},
|
|
32231
32482
|
async ({ workOrderId, title, description, size }) => {
|
|
32232
32483
|
if (title === void 0 && description === void 0 && size === void 0) {
|
|
@@ -32256,7 +32507,7 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32256
32507
|
// the callers who have already stopped sending these.
|
|
32257
32508
|
{
|
|
32258
32509
|
workOrderId: external_exports.string().optional().describe("Ignored \u2014 a Work Order is no longer moveable between Tracks"),
|
|
32259
|
-
newTrackId: external_exports.string().optional().describe("Ignored \u2014 a Track
|
|
32510
|
+
newTrackId: external_exports.string().optional().describe("Ignored \u2014 a Track is no longer a valid parent for Orders")
|
|
32260
32511
|
},
|
|
32261
32512
|
async () => {
|
|
32262
32513
|
return {
|
|
@@ -32458,6 +32709,14 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32458
32709
|
);
|
|
32459
32710
|
}
|
|
32460
32711
|
|
|
32712
|
+
// ../../libs/harmonica-services/src/work-item-ids.ts
|
|
32713
|
+
function normalizeWorkItemIds(value) {
|
|
32714
|
+
if (!Array.isArray(value)) return [];
|
|
32715
|
+
return [...new Set(
|
|
32716
|
+
value.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
|
|
32717
|
+
)];
|
|
32718
|
+
}
|
|
32719
|
+
|
|
32461
32720
|
// ../../libs/harmonica-services/src/mcp/tools/workflow-tools.ts
|
|
32462
32721
|
var TASK_STATUSES = ["pending", "running", "completed", "failed"];
|
|
32463
32722
|
function registerWorkflowTools(server, ctx, client) {
|
|
@@ -32648,17 +32907,19 @@ ${JSON.stringify(task.result, null, 2)}
|
|
|
32648
32907
|
systemId: external_exports.string().min(1).describe("The system ID \u2014 the trusted scope the session is rooted at"),
|
|
32649
32908
|
message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
|
|
32650
32909
|
revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions."),
|
|
32910
|
+
workItemIds: external_exports.array(external_exports.string().min(1)).max(50).optional().describe("Optional Work Items associated with this session at kickoff. Each moves to in_progress when execution starts; the agent manages each item outcome independently."),
|
|
32651
32911
|
parentSessionId: external_exports.string().min(1).optional().describe("Your current Session ID (sess-<uuid>). Pass it when you are kicking off this session as part of your own work \u2014 the new session then nests under yours in the session tree and can report back to you. Omit for a standalone session.")
|
|
32652
32912
|
},
|
|
32653
|
-
async ({ systemId, message, revisionId, parentSessionId }) => {
|
|
32913
|
+
async ({ systemId, message, revisionId, workItemIds, parentSessionId }) => {
|
|
32654
32914
|
try {
|
|
32655
32915
|
await assertSystemExists(client, systemId);
|
|
32656
32916
|
const task = await client.startSession(
|
|
32657
32917
|
systemId,
|
|
32658
32918
|
message,
|
|
32659
32919
|
{ name: ctx.user.name, email: ctx.user.email },
|
|
32660
|
-
revisionId?.trim() || parentSessionId?.trim() ? {
|
|
32920
|
+
revisionId?.trim() || workItemIds?.length || parentSessionId?.trim() ? {
|
|
32661
32921
|
...revisionId?.trim() && { revisionId: revisionId.trim() },
|
|
32922
|
+
...workItemIds?.length && { workItemIds: normalizeWorkItemIds(workItemIds) },
|
|
32662
32923
|
...parentSessionId?.trim() && { parentSessionId: parentSessionId.trim() }
|
|
32663
32924
|
} : void 0
|
|
32664
32925
|
);
|
|
@@ -32886,6 +33147,7 @@ var TOOL_PROFILES = Object.freeze({
|
|
|
32886
33147
|
registerOrganizationTools,
|
|
32887
33148
|
registerTeamspaceTools,
|
|
32888
33149
|
registerMembershipTools,
|
|
33150
|
+
registerManagedRelationshipTools,
|
|
32889
33151
|
registerSystemTools,
|
|
32890
33152
|
registerSystemLifecycleTools,
|
|
32891
33153
|
allow(registerBeatVersionTools, ["relocate_beat_version"])
|
|
@@ -32965,6 +33227,7 @@ function registerAllTools(server, ctx, client, profile) {
|
|
|
32965
33227
|
registerValueVelocityTools(server, ctx, client);
|
|
32966
33228
|
registerNotebookTools(server, ctx, client);
|
|
32967
33229
|
registerAccountTools(server, ctx, client);
|
|
33230
|
+
registerManagedRelationshipTools(server, ctx, client);
|
|
32968
33231
|
registerMovementTools(server, ctx, client);
|
|
32969
33232
|
registerExternalMappingTools(server, ctx, client);
|
|
32970
33233
|
registerWorkItemTools(server, ctx, client);
|
|
@@ -33664,6 +33927,8 @@ function createHttpClient(config2) {
|
|
|
33664
33927
|
return { revision: result.revision, warnings: result.warnings ?? [] };
|
|
33665
33928
|
},
|
|
33666
33929
|
getRevision: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}`),
|
|
33930
|
+
// `request` maps 404 to undefined, which is what absence must be here.
|
|
33931
|
+
resolveRevisionPreview: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}/preview`),
|
|
33667
33932
|
listConceptRevisions: async (systemId, options) => {
|
|
33668
33933
|
const qs = options?.includeArchived ? "?includeArchived=true" : "";
|
|
33669
33934
|
const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/revisions/pending${qs}`);
|
|
@@ -34305,6 +34570,7 @@ function createHttpClient(config2) {
|
|
|
34305
34570
|
// Agent sessions
|
|
34306
34571
|
startSession: async (systemId, message, triggeredBy, options) => {
|
|
34307
34572
|
const trimmedRevisionId = options?.revisionId?.trim();
|
|
34573
|
+
const workItemIds = normalizeWorkItemIds(options?.workItemIds);
|
|
34308
34574
|
const trimmedParentSessionId = options?.parentSessionId?.trim();
|
|
34309
34575
|
const result = await request(
|
|
34310
34576
|
"POST",
|
|
@@ -34314,6 +34580,7 @@ function createHttpClient(config2) {
|
|
|
34314
34580
|
message,
|
|
34315
34581
|
...triggeredBy && { triggeredBy },
|
|
34316
34582
|
...trimmedRevisionId && { revisionId: trimmedRevisionId },
|
|
34583
|
+
...workItemIds?.length && { workItemIds },
|
|
34317
34584
|
...trimmedParentSessionId && { parentSessionId: trimmedParentSessionId }
|
|
34318
34585
|
}
|
|
34319
34586
|
);
|
|
@@ -34952,6 +35219,31 @@ function createHttpClient(config2) {
|
|
|
34952
35219
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/movements`);
|
|
34953
35220
|
return res?.movements ?? [];
|
|
34954
35221
|
},
|
|
35222
|
+
createManagedRelationship: async (input) => {
|
|
35223
|
+
const { agencyOrgId, ...body } = input;
|
|
35224
|
+
const result = await request(
|
|
35225
|
+
"POST",
|
|
35226
|
+
`/api/organizations/${encodeURIComponent(agencyOrgId)}/managed-relationships`,
|
|
35227
|
+
body
|
|
35228
|
+
);
|
|
35229
|
+
if (result === void 0) throw new Error("Resource not found when creating managed relationship");
|
|
35230
|
+
return result;
|
|
35231
|
+
},
|
|
35232
|
+
getManagedRelationship: async (relationshipId) => request("GET", `/api/managed-relationships/${encodeURIComponent(relationshipId)}`),
|
|
35233
|
+
listManagedRelationships: async (agencyOrgId) => {
|
|
35234
|
+
const result = await request(
|
|
35235
|
+
"GET",
|
|
35236
|
+
`/api/organizations/${encodeURIComponent(agencyOrgId)}/managed-relationships`
|
|
35237
|
+
);
|
|
35238
|
+
return result?.relationships ?? [];
|
|
35239
|
+
},
|
|
35240
|
+
setManagedRelationshipStatus: async (relationshipId, status) => {
|
|
35241
|
+
return request(
|
|
35242
|
+
"PATCH",
|
|
35243
|
+
`/api/managed-relationships/${encodeURIComponent(relationshipId)}/status`,
|
|
35244
|
+
{ status }
|
|
35245
|
+
);
|
|
35246
|
+
},
|
|
34955
35247
|
listChildAccounts: async (accountId) => {
|
|
34956
35248
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/children`);
|
|
34957
35249
|
return res?.accounts ?? [];
|
|
@@ -35471,7 +35763,7 @@ function loadConfig() {
|
|
|
35471
35763
|
};
|
|
35472
35764
|
}
|
|
35473
35765
|
async function main() {
|
|
35474
|
-
console.error(`[harmonica-mcp] v${"5.
|
|
35766
|
+
console.error(`[harmonica-mcp] v${"5.3.0"} starting\u2026`);
|
|
35475
35767
|
const config2 = loadConfig();
|
|
35476
35768
|
const client = createHttpClient({
|
|
35477
35769
|
apiBaseUrl: config2.apiBaseUrl,
|