harmony-mcp 1.13.0 → 1.13.2

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.
Files changed (3) hide show
  1. package/dist/cli.js +338 -118
  2. package/dist/index.js +338 -118
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -25371,6 +25371,31 @@ async function extractMidSessionLearnings(client2, ctx) {
25371
25371
  const now = Date.now();
25372
25372
  const entityIds = [];
25373
25373
  const history = sessionTaskHistory.get(ctx.cardId);
25374
+ if (ctx.currentTask) {
25375
+ const previousTask = history?.lastTask || "";
25376
+ const similarity = levenshteinSimilarity(previousTask, ctx.currentTask);
25377
+ const existingSteps = history?.steps || [];
25378
+ if (existingSteps.length === 0 || similarity < 0.6 && previousTask.length > 0) {
25379
+ const newStep = {
25380
+ task: ctx.currentTask,
25381
+ progress: ctx.progressPercent ?? 0,
25382
+ timestamp: now
25383
+ };
25384
+ if (existingSteps.length === 0 && previousTask.length > 0) {
25385
+ existingSteps.push({
25386
+ task: previousTask,
25387
+ progress: 0,
25388
+ timestamp: history?.lastExtractionAt ?? now
25389
+ });
25390
+ }
25391
+ existingSteps.push(newStep);
25392
+ sessionTaskHistory.set(ctx.cardId, {
25393
+ lastTask: ctx.currentTask,
25394
+ lastExtractionAt: history?.lastExtractionAt ?? 0,
25395
+ steps: existingSteps
25396
+ });
25397
+ }
25398
+ }
25374
25399
  if (history && now - history.lastExtractionAt < MID_SESSION_RATE_LIMIT_MS) {
25375
25400
  if (ctx.status !== "blocked" || !ctx.blockers?.length) {
25376
25401
  return { count: 0, entityIds: [] };
@@ -25408,7 +25433,8 @@ Progress: ${ctx.progressPercent ?? "unknown"}%`,
25408
25433
  }
25409
25434
  sessionTaskHistory.set(ctx.cardId, {
25410
25435
  lastTask: ctx.currentTask || "",
25411
- lastExtractionAt: now
25436
+ lastExtractionAt: now,
25437
+ steps: history?.steps || []
25412
25438
  });
25413
25439
  return { count: entityIds.length, entityIds };
25414
25440
  }
@@ -25444,9 +25470,11 @@ Progress: ${ctx.progressPercent ?? "unknown"}%`,
25444
25470
  entityIds.push(entity.id);
25445
25471
  } catch {}
25446
25472
  }
25473
+ const currentHistory = sessionTaskHistory.get(ctx.cardId);
25447
25474
  sessionTaskHistory.set(ctx.cardId, {
25448
25475
  lastTask: ctx.currentTask,
25449
- lastExtractionAt: entityIds.length > 0 ? now : history?.lastExtractionAt ?? 0
25476
+ lastExtractionAt: entityIds.length > 0 ? now : currentHistory?.lastExtractionAt ?? 0,
25477
+ steps: currentHistory?.steps || []
25450
25478
  });
25451
25479
  }
25452
25480
  return { count: entityIds.length, entityIds };
@@ -25454,6 +25482,128 @@ Progress: ${ctx.progressPercent ?? "unknown"}%`,
25454
25482
  function clearMidSessionTracking(cardId) {
25455
25483
  sessionTaskHistory.delete(cardId);
25456
25484
  }
25485
+ function enrichSteps(steps) {
25486
+ return steps.map((step, i) => {
25487
+ const prevProgress = i > 0 ? steps[i - 1].progress : 0;
25488
+ const prevTimestamp = i > 0 ? steps[i - 1].timestamp : step.timestamp;
25489
+ const progressDelta = step.progress - prevProgress;
25490
+ return {
25491
+ task: step.task,
25492
+ progress: step.progress,
25493
+ progressDelta,
25494
+ durationMs: step.timestamp - prevTimestamp,
25495
+ isKeyDecision: progressDelta >= 20
25496
+ };
25497
+ });
25498
+ }
25499
+ function buildProcedureContent(session, enrichedSteps, wikiLinksLine) {
25500
+ const triggerLabels = session.cardLabels.length > 0 ? `Labels: ${session.cardLabels.join(", ")}` : "";
25501
+ const stepsMarkdown = enrichedSteps.map((s, i) => {
25502
+ const marker = s.isKeyDecision ? " **[key step]**" : "";
25503
+ const duration3 = s.durationMs > 0 ? ` (~${Math.round(s.durationMs / 60000)}min)` : "";
25504
+ return `${i + 1}. ${s.task} (${s.progress}%, +${s.progressDelta}%)${marker}${duration3}`;
25505
+ }).join(`
25506
+ `);
25507
+ const subtaskSection = session.cardSubtasks && session.cardSubtasks.length > 0 ? [
25508
+ "",
25509
+ "## Subtasks Completed",
25510
+ ...session.cardSubtasks.map((s) => `- [${s.done ? "x" : " "}] ${s.title}`)
25511
+ ].join(`
25512
+ `) : "";
25513
+ const durationInfo = session.sessionDurationMs ? `
25514
+ Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
25515
+ return [
25516
+ "## Trigger",
25517
+ `When working on: "${session.cardTitle}"`,
25518
+ triggerLabels,
25519
+ "",
25520
+ "## Steps",
25521
+ stepsMarkdown,
25522
+ subtaskSection,
25523
+ "",
25524
+ "## Outcome",
25525
+ `Completed at ${session.progressPercent ?? "unknown"}%`,
25526
+ session.currentTask ? `Final state: ${session.currentTask}` : "",
25527
+ `Agent: ${session.agentName}`,
25528
+ durationInfo,
25529
+ wikiLinksLine
25530
+ ].filter((line) => line !== undefined).join(`
25531
+ `);
25532
+ }
25533
+ async function extractOrReinforceProcedure(client2, session, steps, workspaceId, projectId, wikiLinksLine = "") {
25534
+ const enrichedSteps = enrichSteps(steps);
25535
+ const newContent = buildProcedureContent(session, enrichedSteps, wikiLinksLine);
25536
+ const tags = [
25537
+ "auto-extracted",
25538
+ "procedure",
25539
+ ...session.cardLabels.slice(0, 3)
25540
+ ];
25541
+ try {
25542
+ const similar = await findSimilarEntities(client2, `Procedure: ${session.cardTitle}`, newContent, workspaceId, { projectId, limit: 5, minRrfScore: 0.03 });
25543
+ const matchingProcedure = similar.find((e) => e.type === "procedure" && (e.rrf_score ?? 0) >= 0.05);
25544
+ if (matchingProcedure) {
25545
+ const { entity: rawEntity } = await client2.getMemoryEntity(matchingProcedure.id);
25546
+ const fullEntity = rawEntity;
25547
+ const currentMeta = fullEntity.metadata || {};
25548
+ const sourceCards = (currentMeta.source_cards || []).slice();
25549
+ if (!sourceCards.includes(session.cardId)) {
25550
+ sourceCards.push(session.cardId);
25551
+ }
25552
+ const reuseCount = (currentMeta.reuse_count || 0) + 1;
25553
+ const currentConfidence = fullEntity.confidence ?? 0.7;
25554
+ const newConfidence = Math.min(0.95, currentConfidence + 0.05);
25555
+ const stepsAppendix = enrichedSteps.map((s, i) => `${i + 1}. ${s.task} (${s.progress}%)`).join(`
25556
+ `);
25557
+ const appendix = `
25558
+
25559
+ ---
25560
+ ### Execution ${reuseCount + 1}: ${session.cardTitle}
25561
+ ${stepsAppendix}
25562
+ Agent: ${session.agentName} | ${new Date().toISOString().split("T")[0]}`;
25563
+ const updatedMeta = {
25564
+ ...currentMeta,
25565
+ reuse_count: reuseCount,
25566
+ source_cards: sourceCards,
25567
+ last_reinforced_at: new Date().toISOString(),
25568
+ step_count: Math.max(currentMeta.step_count || 0, steps.length)
25569
+ };
25570
+ const shouldPromote = reuseCount >= 2 && fullEntity.memory_tier !== "reference";
25571
+ await client2.updateMemoryEntity(fullEntity.id, {
25572
+ content: (fullEntity.content || "") + appendix,
25573
+ confidence: newConfidence,
25574
+ metadata: {
25575
+ ...updatedMeta,
25576
+ ...shouldPromote ? {
25577
+ promoted_reason: `Reinforced by ${reuseCount + 1} successful sessions`,
25578
+ promoted_at: new Date().toISOString()
25579
+ } : {}
25580
+ },
25581
+ ...shouldPromote ? { memory_tier: "reference" } : {}
25582
+ });
25583
+ return { mode: "reinforced", entityId: fullEntity.id };
25584
+ }
25585
+ } catch {}
25586
+ return {
25587
+ mode: "created",
25588
+ learning: {
25589
+ title: `Procedure: ${session.cardTitle}`,
25590
+ content: newContent,
25591
+ type: "procedure",
25592
+ tier: "episode",
25593
+ confidence: 0.7,
25594
+ tags,
25595
+ metadata: {
25596
+ source: "active_learning",
25597
+ card_id: session.cardId,
25598
+ source_cards: [session.cardId],
25599
+ step_count: steps.length,
25600
+ key_step_count: enrichedSteps.filter((s) => s.isKeyDecision).length,
25601
+ reuse_count: 0,
25602
+ review_status: "pending"
25603
+ }
25604
+ }
25605
+ };
25606
+ }
25457
25607
  async function extractLearnings(client2, session) {
25458
25608
  const workspaceId = getActiveWorkspaceId();
25459
25609
  if (!workspaceId) {
@@ -25554,6 +25704,19 @@ Agent: ${session.agentName}`,
25554
25704
  });
25555
25705
  }
25556
25706
  const entityIds = [];
25707
+ const stepHistory = sessionTaskHistory.get(session.cardId);
25708
+ const hasEnoughSteps = stepHistory && stepHistory.steps.length >= 2;
25709
+ const isSuccessful = session.status === "completed" && (session.progressPercent === undefined || session.progressPercent >= 85) && !session.blockers?.length;
25710
+ if (isSuccessful && hasEnoughSteps) {
25711
+ const procedureResult = await extractOrReinforceProcedure(client2, session, stepHistory.steps, workspaceId, projectId, wikiLinksLine);
25712
+ if (procedureResult) {
25713
+ if (procedureResult.mode === "created") {
25714
+ learnings.push(procedureResult.learning);
25715
+ } else {
25716
+ entityIds.push(procedureResult.entityId);
25717
+ }
25718
+ }
25719
+ }
25557
25720
  const createdPairs = [];
25558
25721
  for (const learning of learnings) {
25559
25722
  try {
@@ -26456,75 +26619,6 @@ function deriveClusterTitle(cluster, type) {
26456
26619
  return `Consolidated ${type}: ${suffix}`;
26457
26620
  }
26458
26621
 
26459
- // src/lifecycle-maintenance.ts
26460
- async function runLifecycleMaintenance(client3, workspaceId, projectId) {
26461
- const result = {
26462
- archived: 0,
26463
- pruned: 0,
26464
- promoted: 0,
26465
- reviewed: 0,
26466
- errors: 0
26467
- };
26468
- let entities;
26469
- try {
26470
- const listResult = await client3.listMemoryEntities({
26471
- workspace_id: workspaceId,
26472
- project_id: projectId,
26473
- limit: 200
26474
- });
26475
- entities = listResult.entities || [];
26476
- } catch {
26477
- return result;
26478
- }
26479
- if (entities.length === 0)
26480
- return result;
26481
- const now = Date.now();
26482
- const STALE_DRAFT_MAX_AGE_DAYS = 30;
26483
- for (const entity of entities) {
26484
- try {
26485
- const lifecycle2 = evaluateLifecycle(entity);
26486
- if (lifecycle2.shouldArchive) {
26487
- await client3.deleteMemoryEntity(entity.id);
26488
- result.archived++;
26489
- continue;
26490
- }
26491
- if (entity.memory_tier === "draft") {
26492
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
26493
- if (ageDays > STALE_DRAFT_MAX_AGE_DAYS && lifecycle2.decay.score < 0.3) {
26494
- await client3.deleteMemoryEntity(entity.id);
26495
- result.pruned++;
26496
- continue;
26497
- }
26498
- }
26499
- if (lifecycle2.promotion.eligible && lifecycle2.promotion.targetTier) {
26500
- await client3.updateMemoryEntity(entity.id, {
26501
- memory_tier: lifecycle2.promotion.targetTier,
26502
- metadata: {
26503
- promoted_at: new Date().toISOString(),
26504
- promotion_reason: lifecycle2.promotion.reason,
26505
- promoted_from: entity.memory_tier
26506
- }
26507
- });
26508
- result.promoted++;
26509
- continue;
26510
- }
26511
- if (lifecycle2.shouldFlagForReview) {
26512
- await client3.updateMemoryEntity(entity.id, {
26513
- metadata: {
26514
- needs_review: true,
26515
- review_reason: lifecycle2.reviewReason,
26516
- flagged_at: new Date().toISOString()
26517
- }
26518
- });
26519
- result.reviewed++;
26520
- }
26521
- } catch {
26522
- result.errors++;
26523
- }
26524
- }
26525
- return result;
26526
- }
26527
-
26528
26622
  // src/context-assembly.ts
26529
26623
  var DEFAULT_TOKEN_BUDGET = 4000;
26530
26624
  var MAX_TOKENS_PER_ENTITY = 500;
@@ -26931,6 +27025,75 @@ async function recordContextFeedback(client3, cardId, sessionStatus, progressPer
26931
27025
  return { adjusted };
26932
27026
  }
26933
27027
 
27028
+ // src/lifecycle-maintenance.ts
27029
+ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
27030
+ const result = {
27031
+ archived: 0,
27032
+ pruned: 0,
27033
+ promoted: 0,
27034
+ reviewed: 0,
27035
+ errors: 0
27036
+ };
27037
+ let entities;
27038
+ try {
27039
+ const listResult = await client3.listMemoryEntities({
27040
+ workspace_id: workspaceId,
27041
+ project_id: projectId,
27042
+ limit: 200
27043
+ });
27044
+ entities = listResult.entities || [];
27045
+ } catch {
27046
+ return result;
27047
+ }
27048
+ if (entities.length === 0)
27049
+ return result;
27050
+ const now = Date.now();
27051
+ const STALE_DRAFT_MAX_AGE_DAYS = 30;
27052
+ for (const entity of entities) {
27053
+ try {
27054
+ const lifecycle2 = evaluateLifecycle(entity);
27055
+ if (lifecycle2.shouldArchive) {
27056
+ await client3.deleteMemoryEntity(entity.id);
27057
+ result.archived++;
27058
+ continue;
27059
+ }
27060
+ if (entity.memory_tier === "draft") {
27061
+ const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
27062
+ if (ageDays > STALE_DRAFT_MAX_AGE_DAYS && lifecycle2.decay.score < 0.3) {
27063
+ await client3.deleteMemoryEntity(entity.id);
27064
+ result.pruned++;
27065
+ continue;
27066
+ }
27067
+ }
27068
+ if (lifecycle2.promotion.eligible && lifecycle2.promotion.targetTier) {
27069
+ await client3.updateMemoryEntity(entity.id, {
27070
+ memory_tier: lifecycle2.promotion.targetTier,
27071
+ metadata: {
27072
+ promoted_at: new Date().toISOString(),
27073
+ promotion_reason: lifecycle2.promotion.reason,
27074
+ promoted_from: entity.memory_tier
27075
+ }
27076
+ });
27077
+ result.promoted++;
27078
+ continue;
27079
+ }
27080
+ if (lifecycle2.shouldFlagForReview) {
27081
+ await client3.updateMemoryEntity(entity.id, {
27082
+ metadata: {
27083
+ needs_review: true,
27084
+ review_reason: lifecycle2.reviewReason,
27085
+ flagged_at: new Date().toISOString()
27086
+ }
27087
+ });
27088
+ result.reviewed++;
27089
+ }
27090
+ } catch {
27091
+ result.errors++;
27092
+ }
27093
+ }
27094
+ return result;
27095
+ }
27096
+
26934
27097
  // src/prompt-builder.ts
26935
27098
  var LABEL_CATEGORY_MAP = {
26936
27099
  bug: "bug",
@@ -27233,6 +27396,12 @@ ${assembledContext}`);
27233
27396
  sections.push(memory.content);
27234
27397
  }
27235
27398
  }
27399
+ const oneThingLine = synthesizeOneThing(card, subtasks, links);
27400
+ if (oneThingLine) {
27401
+ sections.push(`
27402
+ ## Recommended Next Step
27403
+ ${oneThingLine}`);
27404
+ }
27236
27405
  if (variant === "execute") {
27237
27406
  sections.push(`
27238
27407
  ## Progress Tracking
@@ -27272,6 +27441,32 @@ ${customConstraints}`);
27272
27441
  ...assemblyId && { assemblyId }
27273
27442
  };
27274
27443
  }
27444
+ function synthesizeOneThing(card, subtasks, links) {
27445
+ if (card.done)
27446
+ return null;
27447
+ const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
27448
+ if (blockers.length > 0) {
27449
+ const blocker = blockers[0];
27450
+ return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
27451
+ }
27452
+ if (subtasks.length > 0) {
27453
+ const completed = subtasks.filter((s) => s.completed).length;
27454
+ if (completed === subtasks.length) {
27455
+ return "All subtasks completed. Review the work and mark the card as done.";
27456
+ }
27457
+ const nextSubtask = subtasks.find((s) => !s.completed);
27458
+ if (nextSubtask) {
27459
+ return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
27460
+ }
27461
+ }
27462
+ if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
27463
+ return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
27464
+ }
27465
+ if (card.description) {
27466
+ return "Analyze the description, identify the approach, and begin implementation.";
27467
+ }
27468
+ return null;
27469
+ }
27275
27470
 
27276
27471
  // src/server.ts
27277
27472
  var TOOLS = {
@@ -27834,7 +28029,8 @@ var TOOLS = {
27834
28029
  "commitment",
27835
28030
  "lesson",
27836
28031
  "project",
27837
- "handoff"
28032
+ "handoff",
28033
+ "procedure"
27838
28034
  ],
27839
28035
  description: "Entity type (default: context)"
27840
28036
  },
@@ -27893,7 +28089,8 @@ var TOOLS = {
27893
28089
  "commitment",
27894
28090
  "lesson",
27895
28091
  "project",
27896
- "handoff"
28092
+ "handoff",
28093
+ "procedure"
27897
28094
  ],
27898
28095
  description: "Filter by entity type"
27899
28096
  },
@@ -27953,7 +28150,8 @@ var TOOLS = {
27953
28150
  "commitment",
27954
28151
  "lesson",
27955
28152
  "project",
27956
- "handoff"
28153
+ "handoff",
28154
+ "procedure"
27957
28155
  ],
27958
28156
  description: "New entity type"
27959
28157
  },
@@ -28051,7 +28249,8 @@ var TOOLS = {
28051
28249
  "commitment",
28052
28250
  "lesson",
28053
28251
  "project",
28054
- "handoff"
28252
+ "handoff",
28253
+ "procedure"
28055
28254
  ],
28056
28255
  description: "Filter results by entity type"
28057
28256
  },
@@ -28096,7 +28295,8 @@ var TOOLS = {
28096
28295
  "commitment",
28097
28296
  "lesson",
28098
28297
  "project",
28099
- "handoff"
28298
+ "handoff",
28299
+ "procedure"
28100
28300
  ],
28101
28301
  description: "Filter by entity type"
28102
28302
  },
@@ -28683,7 +28883,7 @@ async function handleToolCall(name, args, deps) {
28683
28883
  };
28684
28884
  switch (name) {
28685
28885
  case "harmony_create_card": {
28686
- const title = exports_external.string().min(1).parse(args.title);
28886
+ const title = exports_external.string().min(1).max(500).parse(args.title);
28687
28887
  const projectId = args.projectId || getProjectId();
28688
28888
  const result = await client3.createCard(projectId, {
28689
28889
  title,
@@ -28709,7 +28909,8 @@ async function handleToolCall(name, args, deps) {
28709
28909
  case "harmony_move_card": {
28710
28910
  const cardId = exports_external.string().uuid().parse(args.cardId);
28711
28911
  const columnId = exports_external.string().uuid().parse(args.columnId);
28712
- const result = await client3.moveCard(cardId, columnId, args.position);
28912
+ const position = args.position !== undefined ? exports_external.number().int().min(0).parse(args.position) : undefined;
28913
+ const result = await client3.moveCard(cardId, columnId, position);
28713
28914
  let sessionEnded = false;
28714
28915
  try {
28715
28916
  const { card } = result;
@@ -28754,7 +28955,7 @@ async function handleToolCall(name, args, deps) {
28754
28955
  return { success: true, ...result };
28755
28956
  }
28756
28957
  case "harmony_search_cards": {
28757
- const query = exports_external.string().min(1).parse(args.query);
28958
+ const query = exports_external.string().min(1).max(500).parse(args.query);
28758
28959
  const result = await client3.searchCards(query, {
28759
28960
  projectId: args.projectId
28760
28961
  });
@@ -28772,14 +28973,14 @@ async function handleToolCall(name, args, deps) {
28772
28973
  return { success: true, ...result };
28773
28974
  }
28774
28975
  case "harmony_create_column": {
28775
- const name2 = exports_external.string().min(1).parse(args.name);
28976
+ const name2 = exports_external.string().min(1).max(100).parse(args.name);
28776
28977
  const projectId = args.projectId || getProjectId();
28777
28978
  const result = await client3.createColumn(projectId, name2);
28778
28979
  return { success: true, ...result };
28779
28980
  }
28780
28981
  case "harmony_update_column": {
28781
28982
  const columnId = exports_external.string().uuid().parse(args.columnId);
28782
- const name2 = exports_external.string().min(1).parse(args.name);
28983
+ const name2 = exports_external.string().min(1).max(100).parse(args.name);
28783
28984
  const result = await client3.updateColumn(columnId, name2);
28784
28985
  return { success: true, ...result };
28785
28986
  }
@@ -28789,7 +28990,7 @@ async function handleToolCall(name, args, deps) {
28789
28990
  return { success: true };
28790
28991
  }
28791
28992
  case "harmony_create_label": {
28792
- const name2 = exports_external.string().min(1).parse(args.name);
28993
+ const name2 = exports_external.string().min(1).max(50).parse(args.name);
28793
28994
  const color = exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).parse(args.color);
28794
28995
  const projectId = args.projectId || getProjectId();
28795
28996
  const board = await client3.getBoard(projectId, { summary: true });
@@ -28831,7 +29032,7 @@ async function handleToolCall(name, args, deps) {
28831
29032
  }
28832
29033
  case "harmony_create_subtask": {
28833
29034
  const cardId = exports_external.string().uuid().parse(args.cardId);
28834
- const title = exports_external.string().min(1).parse(args.title);
29035
+ const title = exports_external.string().min(1).max(500).parse(args.title);
28835
29036
  const result = await client3.createSubtask(cardId, title);
28836
29037
  return { success: true, ...result };
28837
29038
  }
@@ -28895,7 +29096,7 @@ async function handleToolCall(name, args, deps) {
28895
29096
  };
28896
29097
  }
28897
29098
  case "harmony_process_command": {
28898
- const command = exports_external.string().min(1).parse(args.command);
29099
+ const command = exports_external.string().min(1).max(500).parse(args.command);
28899
29100
  const result = await client3.processNLU({
28900
29101
  command,
28901
29102
  projectId: args.projectId || deps.getActiveProjectId() || undefined,
@@ -28907,8 +29108,8 @@ async function handleToolCall(name, args, deps) {
28907
29108
  }
28908
29109
  case "harmony_start_agent_session": {
28909
29110
  const cardId = exports_external.string().uuid().parse(args.cardId);
28910
- const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
28911
- const agentName = exports_external.string().min(1).parse(args.agentName);
29111
+ const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
29112
+ const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
28912
29113
  const moveToColumn = args.moveToColumn;
28913
29114
  const addLabels = args.addLabels;
28914
29115
  let movedTo = null;
@@ -29007,13 +29208,14 @@ async function handleToolCall(name, args, deps) {
29007
29208
  }
29008
29209
  case "harmony_update_agent_progress": {
29009
29210
  const cardId = exports_external.string().uuid().parse(args.cardId);
29010
- const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
29011
- const agentName = exports_external.string().min(1).parse(args.agentName);
29211
+ const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
29212
+ const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
29213
+ const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
29012
29214
  const result = await client3.updateAgentProgress(cardId, {
29013
29215
  agentIdentifier,
29014
29216
  agentName,
29015
29217
  status: args.status,
29016
- progressPercent: args.progressPercent,
29218
+ progressPercent,
29017
29219
  currentTask: args.currentTask,
29018
29220
  blockers: args.blockers,
29019
29221
  estimatedMinutesRemaining: args.estimatedMinutesRemaining
@@ -29030,7 +29232,7 @@ async function handleToolCall(name, args, deps) {
29030
29232
  currentTask: args.currentTask,
29031
29233
  status: args.status,
29032
29234
  blockers: args.blockers,
29033
- progressPercent: args.progressPercent
29235
+ progressPercent
29034
29236
  });
29035
29237
  midSessionLearnings = midResult.count;
29036
29238
  } catch {}
@@ -29040,19 +29242,27 @@ async function handleToolCall(name, args, deps) {
29040
29242
  const cardId = exports_external.string().uuid().parse(args.cardId);
29041
29243
  const moveToColumn = args.moveToColumn;
29042
29244
  const sessionStatus = args.status || "completed";
29245
+ const endProgressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
29043
29246
  const result = await client3.endAgentSession(cardId, {
29044
29247
  status: sessionStatus,
29045
- progressPercent: args.progressPercent
29248
+ progressPercent: endProgressPercent
29046
29249
  });
29047
29250
  let movedTo = null;
29048
29251
  let learningsExtracted = 0;
29049
29252
  let cardTitle = "";
29050
29253
  let cardLabels = [];
29254
+ let cardDescription = "";
29255
+ let cardSubtasks = [];
29051
29256
  try {
29052
29257
  const { card } = await client3.getCard(cardId);
29053
29258
  const typedCard = card;
29054
29259
  cardTitle = typedCard.title || "";
29055
29260
  cardLabels = (typedCard.labels || []).map((l) => l.name);
29261
+ cardDescription = typedCard.description || "";
29262
+ cardSubtasks = (typedCard.subtasks || []).map((s) => ({
29263
+ title: s.title,
29264
+ done: s.done
29265
+ }));
29056
29266
  const projectId = typedCard.project_id;
29057
29267
  if (moveToColumn && projectId) {
29058
29268
  const board = await client3.getBoard(projectId, {
@@ -29068,6 +29278,13 @@ async function handleToolCall(name, args, deps) {
29068
29278
  } catch {}
29069
29279
  try {
29070
29280
  const session = result.session;
29281
+ let sessionDurationMs;
29282
+ if (session?.created_at) {
29283
+ const startTime = new Date(session.created_at).getTime();
29284
+ if (!Number.isNaN(startTime)) {
29285
+ sessionDurationMs = Date.now() - startTime;
29286
+ }
29287
+ }
29071
29288
  const sessionContext = {
29072
29289
  cardId,
29073
29290
  cardTitle,
@@ -29075,9 +29292,12 @@ async function handleToolCall(name, args, deps) {
29075
29292
  agentIdentifier: session?.agent_identifier || "unknown",
29076
29293
  agentName: session?.agent_name || "Unknown Agent",
29077
29294
  status: sessionStatus,
29078
- progressPercent: args.progressPercent,
29295
+ progressPercent: endProgressPercent,
29079
29296
  blockers: session?.blockers || undefined,
29080
- currentTask: session?.current_task || undefined
29297
+ currentTask: session?.current_task || undefined,
29298
+ sessionDurationMs,
29299
+ cardDescription: cardDescription || undefined,
29300
+ cardSubtasks: cardSubtasks.length > 0 ? cardSubtasks : undefined
29081
29301
  };
29082
29302
  const learningResult = await extractLearnings(client3, sessionContext);
29083
29303
  learningsExtracted = learningResult.count;
@@ -29085,7 +29305,7 @@ async function handleToolCall(name, args, deps) {
29085
29305
  let feedbackAdjusted = 0;
29086
29306
  try {
29087
29307
  const session = result.session;
29088
- const feedbackResult = await recordContextFeedback(client3, cardId, sessionStatus, args.progressPercent, (session?.blockers?.length ?? 0) > 0);
29308
+ const feedbackResult = await recordContextFeedback(client3, cardId, sessionStatus, endProgressPercent, (session?.blockers?.length ?? 0) > 0);
29089
29309
  feedbackAdjusted = feedbackResult.adjusted;
29090
29310
  } catch {}
29091
29311
  let maintenanceResult;
@@ -29233,8 +29453,8 @@ async function handleToolCall(name, args, deps) {
29233
29453
  };
29234
29454
  }
29235
29455
  case "harmony_remember": {
29236
- const title = exports_external.string().min(1).parse(args.title);
29237
- const content = exports_external.string().min(1).parse(args.content);
29456
+ const title = exports_external.string().min(1).max(300).parse(args.title);
29457
+ const content = exports_external.string().min(1).max(50000).parse(args.content);
29238
29458
  const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
29239
29459
  if (!workspaceId) {
29240
29460
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
@@ -29250,7 +29470,7 @@ async function handleToolCall(name, args, deps) {
29250
29470
  title,
29251
29471
  content,
29252
29472
  metadata: args.metadata,
29253
- confidence: args.confidence,
29473
+ confidence: args.confidence !== undefined ? exports_external.number().min(0).max(1).parse(args.confidence) : undefined,
29254
29474
  tags: entityTags.length > 0 ? entityTags : undefined,
29255
29475
  agent_identifier: "claude-code"
29256
29476
  });
@@ -29334,9 +29554,9 @@ async function handleToolCall(name, args, deps) {
29334
29554
  const entityId = exports_external.string().uuid().parse(args.entityId);
29335
29555
  const updates = {};
29336
29556
  if (args.title !== undefined)
29337
- updates.title = args.title;
29557
+ updates.title = exports_external.string().min(1).max(300).parse(args.title);
29338
29558
  if (args.content !== undefined)
29339
- updates.content = args.content;
29559
+ updates.content = exports_external.string().max(50000).parse(args.content);
29340
29560
  if (args.type !== undefined)
29341
29561
  updates.type = args.type;
29342
29562
  if (args.scope !== undefined)
@@ -29344,7 +29564,7 @@ async function handleToolCall(name, args, deps) {
29344
29564
  if (args.tags !== undefined)
29345
29565
  updates.tags = args.tags;
29346
29566
  if (args.confidence !== undefined)
29347
- updates.confidence = args.confidence;
29567
+ updates.confidence = exports_external.number().min(0).max(1).parse(args.confidence);
29348
29568
  if (args.metadata !== undefined)
29349
29569
  updates.metadata = args.metadata;
29350
29570
  const result = await client3.updateMemoryEntity(entityId, updates);
@@ -29372,12 +29592,12 @@ async function handleToolCall(name, args, deps) {
29372
29592
  source_id: sourceId,
29373
29593
  target_id: targetId,
29374
29594
  relation_type: relationType,
29375
- confidence: args.confidence
29595
+ confidence: args.confidence !== undefined ? exports_external.number().min(0).max(1).parse(args.confidence) : undefined
29376
29596
  });
29377
29597
  return { success: true, ...result };
29378
29598
  }
29379
29599
  case "harmony_memory_search": {
29380
- const query = exports_external.string().min(1).parse(args.query);
29600
+ const query = exports_external.string().min(1).max(500).parse(args.query);
29381
29601
  const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
29382
29602
  if (!workspaceId) {
29383
29603
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
@@ -29474,7 +29694,7 @@ async function handleToolCall(name, args, deps) {
29474
29694
  };
29475
29695
  }
29476
29696
  case "harmony_create_plan": {
29477
- const title = exports_external.string().min(1).parse(args.title);
29697
+ const title = exports_external.string().min(1).max(200).parse(args.title);
29478
29698
  const projectId = args.projectId || getProjectId();
29479
29699
  const result = await client3.createPlan(projectId, {
29480
29700
  title,
@@ -29521,7 +29741,7 @@ async function handleToolCall(name, args, deps) {
29521
29741
  const planId = exports_external.string().uuid().parse(args.planId);
29522
29742
  const updates = {};
29523
29743
  if (args.title !== undefined)
29524
- updates.title = exports_external.string().min(1).parse(args.title);
29744
+ updates.title = exports_external.string().min(1).max(200).parse(args.title);
29525
29745
  if (args.content !== undefined)
29526
29746
  updates.content = args.content;
29527
29747
  if (args.status !== undefined) {
@@ -29661,7 +29881,7 @@ async function handleToolCall(name, args, deps) {
29661
29881
  }
29662
29882
  case "harmony_debug_context": {
29663
29883
  const entityId = exports_external.string().uuid().parse(args.entityId);
29664
- const taskContext = exports_external.string().min(1).parse(args.taskContext);
29884
+ const taskContext = exports_external.string().min(1).max(2000).parse(args.taskContext);
29665
29885
  const cardLabels = args.cardLabels || [];
29666
29886
  const entityResult = await client3.getMemoryEntity(entityId);
29667
29887
  const entity = mapToContextEntity(entityResult.entity);
@@ -29875,7 +30095,7 @@ async function handleToolCall(name, args, deps) {
29875
30095
  const entityId = exports_external.string().uuid().parse(args.entityId);
29876
30096
  const action = exports_external.enum(["approve", "reject"]).parse(args.action);
29877
30097
  if (action === "approve") {
29878
- await approveLearning(client3, entityId, args.confidence);
30098
+ await approveLearning(client3, entityId, args.confidence !== undefined ? exports_external.number().min(0).max(1).parse(args.confidence) : undefined);
29879
30099
  return {
29880
30100
  success: true,
29881
30101
  action: "approved",
@@ -29890,7 +30110,7 @@ async function handleToolCall(name, args, deps) {
29890
30110
  };
29891
30111
  }
29892
30112
  case "harmony_get_context_manifest": {
29893
- const assemblyId = exports_external.string().min(1).parse(args.assemblyId);
30113
+ const assemblyId = exports_external.string().min(1).max(100).parse(args.assemblyId);
29894
30114
  const manifest = getCachedManifest(assemblyId);
29895
30115
  if (!manifest) {
29896
30116
  throw new Error(`Manifest not found for assembly '${assemblyId}'. Manifests are cached in-memory and expire after server restart.`);
@@ -29909,7 +30129,7 @@ async function handleToolCall(name, args, deps) {
29909
30129
  case "harmony_promote_memory": {
29910
30130
  const entityId = exports_external.string().uuid().parse(args.entityId);
29911
30131
  const targetTier = exports_external.enum(["episode", "reference"]).parse(args.targetTier);
29912
- const reason = exports_external.string().min(1).parse(args.reason);
30132
+ const reason = exports_external.string().min(1).max(500).parse(args.reason);
29913
30133
  const entityResult = await client3.getMemoryEntity(entityId);
29914
30134
  const entity = entityResult.entity;
29915
30135
  const currentTier = entity.memory_tier || "reference";
@@ -29936,9 +30156,9 @@ async function handleToolCall(name, args, deps) {
29936
30156
  };
29937
30157
  }
29938
30158
  case "harmony_signup": {
29939
- const email3 = exports_external.string().email().parse(args.email);
29940
- const password = exports_external.string().min(8).parse(args.password);
29941
- const fullName = exports_external.string().min(1).parse(args.fullName);
30159
+ const email3 = exports_external.string().email().max(254).parse(args.email);
30160
+ const password = exports_external.string().min(8).max(128).parse(args.password);
30161
+ const fullName = exports_external.string().min(1).max(100).parse(args.fullName);
29942
30162
  const apiUrl = deps.getApiUrl();
29943
30163
  const result = await signupUser(apiUrl, {
29944
30164
  email: email3,
@@ -29952,7 +30172,7 @@ async function handleToolCall(name, args, deps) {
29952
30172
  };
29953
30173
  }
29954
30174
  case "harmony_create_workspace": {
29955
- const name2 = exports_external.string().min(1).parse(args.name);
30175
+ const name2 = exports_external.string().min(1).max(100).parse(args.name);
29956
30176
  const result = await client3.createWorkspace({
29957
30177
  name: name2,
29958
30178
  description: args.description
@@ -29961,7 +30181,7 @@ async function handleToolCall(name, args, deps) {
29961
30181
  }
29962
30182
  case "harmony_create_project": {
29963
30183
  const workspaceId = exports_external.string().uuid().parse(args.workspaceId);
29964
- const name2 = exports_external.string().min(1).parse(args.name);
30184
+ const name2 = exports_external.string().min(1).max(100).parse(args.name);
29965
30185
  const result = await client3.createProject({
29966
30186
  workspaceId,
29967
30187
  name: name2,
@@ -29973,7 +30193,7 @@ async function handleToolCall(name, args, deps) {
29973
30193
  }
29974
30194
  case "harmony_send_invitations": {
29975
30195
  const workspaceId = exports_external.string().uuid().parse(args.workspaceId);
29976
- const emails = exports_external.array(exports_external.string().email()).min(1).parse(args.emails);
30196
+ const emails = exports_external.array(exports_external.string().email().max(254)).min(1).parse(args.emails);
29977
30197
  const result = await client3.sendInvitations({
29978
30198
  workspaceId,
29979
30199
  emails,
@@ -29983,14 +30203,14 @@ async function handleToolCall(name, args, deps) {
29983
30203
  return { success: true, ...result };
29984
30204
  }
29985
30205
  case "harmony_generate_api_key": {
29986
- const name2 = exports_external.string().min(1).parse(args.name);
30206
+ const name2 = exports_external.string().min(1).max(100).parse(args.name);
29987
30207
  const result = await client3.generateApiKey(name2);
29988
30208
  return { success: true, ...result };
29989
30209
  }
29990
30210
  case "harmony_onboard": {
29991
- const email3 = exports_external.string().email().parse(args.email);
29992
- const password = exports_external.string().min(8).parse(args.password);
29993
- const fullName = exports_external.string().min(1).parse(args.fullName);
30211
+ const email3 = exports_external.string().email().max(254).parse(args.email);
30212
+ const password = exports_external.string().min(8).max(128).parse(args.password);
30213
+ const fullName = exports_external.string().min(1).max(100).parse(args.fullName);
29994
30214
  const workspaceName = args.workspaceName || `${fullName}'s Workspace`;
29995
30215
  const projectName = args.projectName || "My First Project";
29996
30216
  const template = args.template || "kanban";