harmony-mcp 1.14.0 → 1.14.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.
- package/README.md +34 -4
- package/dist/cli.js +429 -129
- package/dist/index.js +429 -129
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Enables AI coding agents (Claude Code, OpenAI Codex, Cursor, Windsurf) to intera
|
|
|
5
5
|
|
|
6
6
|
## Features
|
|
7
7
|
|
|
8
|
-
- **
|
|
8
|
+
- **61+ MCP Tools** for full board control, knowledge graph, and workflow plans
|
|
9
9
|
- **Knowledge Graph Memory** - persistent memory with entity types, tiers, scopes, and typed relations
|
|
10
10
|
- **Active Learning** - auto-extracts lessons, solutions, and error patterns from completed work sessions
|
|
11
11
|
- **Context Assembly** - token-budget-aware memory injection into AI prompts
|
|
@@ -13,6 +13,7 @@ Enables AI coding agents (Claude Code, OpenAI Codex, Cursor, Windsurf) to intera
|
|
|
13
13
|
- **Card Linking** - create relationships between cards (blocks, relates_to, duplicates, is_part_of)
|
|
14
14
|
- **Prompt Builder** - generate AI-ready prompts from cards with context
|
|
15
15
|
- **Agent Session Tracking** - track work progress with timer badges
|
|
16
|
+
- **Auto-Session Detection** - automatically starts/ends sessions when agents interact with cards
|
|
16
17
|
- **Auto-Assignment** - automatically assign cards to you when starting agent sessions
|
|
17
18
|
- **Memory Sync** - bidirectional sync between local markdown files and remote database
|
|
18
19
|
- **Multi-Agent Support** - works with Claude Code, Codex, Cursor, Windsurf
|
|
@@ -89,7 +90,7 @@ If you prefer to configure manually (e.g., in Claude.ai's UI):
|
|
|
89
90
|
1. Get an API key from [Harmony](https://gethmy.com/user/keys)
|
|
90
91
|
2. In Claude.ai, add a remote MCP server with URL `https://mcp.gethmy.com/mcp`
|
|
91
92
|
3. Set the Authorization header to `Bearer hmy_your_key_here`
|
|
92
|
-
4. All
|
|
93
|
+
4. All 61+ Harmony tools become available in your conversation
|
|
93
94
|
|
|
94
95
|
**Session management** is automatic - sessions have a 1-hour TTL and are created/renewed transparently.
|
|
95
96
|
|
|
@@ -167,6 +168,8 @@ When you start working on a card (e.g., `/hmy #42`):
|
|
|
167
168
|
- `harmony_create_card` - Create a new card
|
|
168
169
|
- `harmony_update_card` - Update card properties
|
|
169
170
|
- `harmony_move_card` - Move card to different column (auto-ends agent session on move to done/review)
|
|
171
|
+
- `harmony_archive_card` - Archive a card (soft-delete, can be restored)
|
|
172
|
+
- `harmony_unarchive_card` - Restore an archived card back to the board
|
|
170
173
|
- `harmony_delete_card` - Delete a card
|
|
171
174
|
- `harmony_assign_card` - Assign to team member
|
|
172
175
|
- `harmony_search_cards` - Search by title/description
|
|
@@ -209,7 +212,7 @@ When you start working on a card (e.g., `/hmy #42`):
|
|
|
209
212
|
|
|
210
213
|
- `harmony_list_workspaces` - List workspaces
|
|
211
214
|
- `harmony_list_projects` - List projects
|
|
212
|
-
- `harmony_get_board` - Get board state (supports pagination via `limit`/`offset`, `summary` mode, `columnId` filter)
|
|
215
|
+
- `harmony_get_board` - Get board state (supports pagination via `limit`/`offset`, `summary` mode, `columnId` filter, `includeArchived`)
|
|
213
216
|
- `harmony_get_workspace_members` - Get team members
|
|
214
217
|
- `harmony_set_workspace_context` - Set active workspace
|
|
215
218
|
- `harmony_set_project_context` - Set active project
|
|
@@ -225,6 +228,20 @@ When you start working on a card (e.g., `/hmy #42`):
|
|
|
225
228
|
- `harmony_update_agent_progress` - Update progress, status, blockers
|
|
226
229
|
- `harmony_end_agent_session` - End session (completed/paused); triggers active learning extraction
|
|
227
230
|
- `harmony_get_agent_session` - Get current session state
|
|
231
|
+
- `harmony_get_agent_profile` - Get aggregate performance profile for an agent
|
|
232
|
+
|
|
233
|
+
### Auto-Session Detection
|
|
234
|
+
|
|
235
|
+
Sessions are automatically started when agents call card-mutating tools without an explicit `harmony_start_agent_session`. This ensures work is always tracked, even when agents don't explicitly manage sessions.
|
|
236
|
+
|
|
237
|
+
**Trigger tools:** `harmony_generate_prompt`, `harmony_update_card`, `harmony_move_card`, `harmony_create_subtask`, `harmony_toggle_subtask`, `harmony_add_label_to_card`, `harmony_remove_label_from_card`
|
|
238
|
+
|
|
239
|
+
**Behavior:**
|
|
240
|
+
- Auto-starts a session when any trigger tool is called on a card without an active session
|
|
241
|
+
- Auto-ends after 10 minutes of inactivity
|
|
242
|
+
- Switching to a different card auto-ends the previous auto-session
|
|
243
|
+
- Explicitly started sessions (via `harmony_start_agent_session`) are never auto-ended
|
|
244
|
+
- Auto-ended sessions trigger the full active learning pipeline
|
|
228
245
|
|
|
229
246
|
### Prompt Generation
|
|
230
247
|
|
|
@@ -256,7 +273,7 @@ Store and retrieve persistent knowledge across sessions. Memories have types, ti
|
|
|
256
273
|
- `harmony_memory_search` - Full-text search across the knowledge base
|
|
257
274
|
- `harmony_relate` - Create a typed relationship between two memory entities
|
|
258
275
|
|
|
259
|
-
**Entity Types:** `agent`, `task`, `decision`, `context`, `pattern`, `error`, `solution`, `preference`, `relationship`, `commitment`, `lesson`, `project`, `handoff`
|
|
276
|
+
**Entity Types:** `agent`, `task`, `decision`, `context`, `pattern`, `error`, `solution`, `preference`, `relationship`, `commitment`, `lesson`, `project`, `handoff`, `procedure`
|
|
260
277
|
|
|
261
278
|
**Memory Tiers:** `draft` (working notes) → `episode` (session summaries) → `reference` (durable knowledge)
|
|
262
279
|
|
|
@@ -274,6 +291,9 @@ Store and retrieve persistent knowledge across sessions. Memories have types, ti
|
|
|
274
291
|
|
|
275
292
|
- `harmony_promote_memory` - Promote entity tier: `draft` → `episode` or `episode` → `reference`
|
|
276
293
|
- `harmony_prune_draft` - Remove stale draft memories (dry-run mode by default)
|
|
294
|
+
- `harmony_consolidate_memories` - Cluster similar draft/episode memories and merge into reference entities (dry-run by default)
|
|
295
|
+
- `harmony_backfill_embeddings` - Generate vector embeddings for entities missing them
|
|
296
|
+
- `harmony_backfill_relations` - Retroactively create semantic relations across existing entities
|
|
277
297
|
|
|
278
298
|
### Context Debugging
|
|
279
299
|
|
|
@@ -285,6 +305,7 @@ Store and retrieve persistent knowledge across sessions. Memories have types, ti
|
|
|
285
305
|
|
|
286
306
|
Create and manage project plans with a phased workflow: **plan** → **execute** → **verify** → **done**.
|
|
287
307
|
|
|
308
|
+
- `harmony_list_plans` - List plans in a project (filter by status or workflow phase)
|
|
288
309
|
- `harmony_create_plan` - Create a new plan with embedded tasks
|
|
289
310
|
- `harmony_get_plan` - Get plan by ID or card ID
|
|
290
311
|
- `harmony_update_plan` - Update plan title, content, status, or phase
|
|
@@ -293,6 +314,15 @@ Create and manage project plans with a phased workflow: **plan** → **execute**
|
|
|
293
314
|
- **execute → verify:** checks card completion status
|
|
294
315
|
- **verify → done:** archives plan, creates memory entities
|
|
295
316
|
|
|
317
|
+
### Onboarding & Account
|
|
318
|
+
|
|
319
|
+
- `harmony_onboard` - Complete end-to-end onboarding: signup → workspace → project → API key
|
|
320
|
+
- `harmony_signup` - Create a new user account
|
|
321
|
+
- `harmony_create_workspace` - Create a new workspace
|
|
322
|
+
- `harmony_create_project` - Create a new project with template columns (kanban, scrum, or simple)
|
|
323
|
+
- `harmony_send_invitations` - Send workspace invitations to team members
|
|
324
|
+
- `harmony_generate_api_key` - Generate an API key for the authenticated user
|
|
325
|
+
|
|
296
326
|
## Direct API Access
|
|
297
327
|
|
|
298
328
|
You can also call the Harmony API directly from any HTTP client:
|
package/dist/cli.js
CHANGED
|
@@ -25315,7 +25315,8 @@ async function findSimilarEntities(client2, title, content, workspaceId, options
|
|
|
25315
25315
|
try {
|
|
25316
25316
|
const { entities } = await client2.searchMemoryEntities(workspaceId, query, {
|
|
25317
25317
|
project_id: options?.projectId,
|
|
25318
|
-
limit: options?.limit ?? 20
|
|
25318
|
+
limit: options?.limit ?? 20,
|
|
25319
|
+
type: options?.type
|
|
25319
25320
|
});
|
|
25320
25321
|
const minScore = options?.minRrfScore ?? 0;
|
|
25321
25322
|
const excludeSet = new Set(options?.excludeIds || []);
|
|
@@ -25330,6 +25331,57 @@ async function findSimilarEntities(client2, title, content, workspaceId, options
|
|
|
25330
25331
|
return [];
|
|
25331
25332
|
}
|
|
25332
25333
|
}
|
|
25334
|
+
var CAUSAL_LOOKUP = [
|
|
25335
|
+
{
|
|
25336
|
+
sourceType: "error",
|
|
25337
|
+
targetType: "solution",
|
|
25338
|
+
relation: "resolved_by",
|
|
25339
|
+
direction: "forward"
|
|
25340
|
+
},
|
|
25341
|
+
{
|
|
25342
|
+
sourceType: "solution",
|
|
25343
|
+
targetType: "error",
|
|
25344
|
+
relation: "resolved_by",
|
|
25345
|
+
direction: "reverse"
|
|
25346
|
+
},
|
|
25347
|
+
{
|
|
25348
|
+
sourceType: "lesson",
|
|
25349
|
+
targetType: "error",
|
|
25350
|
+
relation: "learned_from",
|
|
25351
|
+
direction: "forward"
|
|
25352
|
+
}
|
|
25353
|
+
];
|
|
25354
|
+
async function linkCrossTypeNeighbors(client2, entityId, entityType, title, content, workspaceId, projectId) {
|
|
25355
|
+
const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
|
|
25356
|
+
if (rules.length === 0)
|
|
25357
|
+
return { relationsCreated: 0 };
|
|
25358
|
+
let relationsCreated = 0;
|
|
25359
|
+
for (const rule of rules) {
|
|
25360
|
+
try {
|
|
25361
|
+
const matches = await findSimilarEntities(client2, title, content, workspaceId, {
|
|
25362
|
+
projectId,
|
|
25363
|
+
limit: 10,
|
|
25364
|
+
minRrfScore: 0.04,
|
|
25365
|
+
excludeIds: [entityId],
|
|
25366
|
+
type: rule.targetType
|
|
25367
|
+
});
|
|
25368
|
+
for (const match of matches.slice(0, 3)) {
|
|
25369
|
+
const sourceId = rule.direction === "forward" ? entityId : match.id;
|
|
25370
|
+
const targetId = rule.direction === "forward" ? match.id : entityId;
|
|
25371
|
+
try {
|
|
25372
|
+
await client2.createMemoryRelation({
|
|
25373
|
+
source_id: sourceId,
|
|
25374
|
+
target_id: targetId,
|
|
25375
|
+
relation_type: rule.relation,
|
|
25376
|
+
confidence: 0.65
|
|
25377
|
+
});
|
|
25378
|
+
relationsCreated++;
|
|
25379
|
+
} catch {}
|
|
25380
|
+
}
|
|
25381
|
+
} catch {}
|
|
25382
|
+
}
|
|
25383
|
+
return { relationsCreated };
|
|
25384
|
+
}
|
|
25333
25385
|
|
|
25334
25386
|
// src/active-learning.ts
|
|
25335
25387
|
var CONTRADICTION_TYPES = new Set([
|
|
@@ -25602,6 +25654,31 @@ Agent: ${session.agentName} | ${new Date().toISOString().split("T")[0]}`;
|
|
|
25602
25654
|
}
|
|
25603
25655
|
};
|
|
25604
25656
|
}
|
|
25657
|
+
var SESSION_CAUSAL_RULES = [
|
|
25658
|
+
{ sourceType: "error", targetType: "solution", relation: "resolved_by", confidence: 0.8 },
|
|
25659
|
+
{ sourceType: "lesson", targetType: "error", relation: "learned_from", confidence: 0.75 }
|
|
25660
|
+
];
|
|
25661
|
+
async function linkSessionEntities(client2, createdPairs, _workspaceId, _projectId) {
|
|
25662
|
+
let relationsCreated = 0;
|
|
25663
|
+
for (const rule of SESSION_CAUSAL_RULES) {
|
|
25664
|
+
const sources = createdPairs.filter((p) => p.learning.type === rule.sourceType);
|
|
25665
|
+
const targets = createdPairs.filter((p) => p.learning.type === rule.targetType);
|
|
25666
|
+
for (const source of sources) {
|
|
25667
|
+
for (const target of targets) {
|
|
25668
|
+
try {
|
|
25669
|
+
await client2.createMemoryRelation({
|
|
25670
|
+
source_id: source.id,
|
|
25671
|
+
target_id: target.id,
|
|
25672
|
+
relation_type: rule.relation,
|
|
25673
|
+
confidence: rule.confidence
|
|
25674
|
+
});
|
|
25675
|
+
relationsCreated++;
|
|
25676
|
+
} catch {}
|
|
25677
|
+
}
|
|
25678
|
+
}
|
|
25679
|
+
}
|
|
25680
|
+
return { relationsCreated };
|
|
25681
|
+
}
|
|
25605
25682
|
async function extractLearnings(client2, session) {
|
|
25606
25683
|
const workspaceId = getActiveWorkspaceId();
|
|
25607
25684
|
if (!workspaceId) {
|
|
@@ -25743,10 +25820,17 @@ Agent: ${session.agentName}`,
|
|
|
25743
25820
|
for (const { id, learning } of createdPairs) {
|
|
25744
25821
|
autoExpandGraph(client2, id, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
25745
25822
|
detectContradictions(client2, id, learning.type, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
25823
|
+
linkCrossTypeNeighbors(client2, id, learning.type, learning.title, learning.content, workspaceId, projectId).catch(() => {});
|
|
25824
|
+
}
|
|
25825
|
+
if (createdPairs.length >= 2) {
|
|
25826
|
+
linkSessionEntities(client2, createdPairs, workspaceId, projectId).catch(() => {});
|
|
25746
25827
|
}
|
|
25747
25828
|
if (entityIds.length > 0) {
|
|
25748
25829
|
detectAndCreatePatterns(client2, entityIds, session, workspaceId, projectId).catch(() => {});
|
|
25749
25830
|
}
|
|
25831
|
+
if (createdPairs.length > 0) {
|
|
25832
|
+
detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId).catch(() => {});
|
|
25833
|
+
}
|
|
25750
25834
|
clearMidSessionTracking(session.cardId);
|
|
25751
25835
|
return { count: entityIds.length, entityIds };
|
|
25752
25836
|
}
|
|
@@ -25837,6 +25921,122 @@ ${memberTitles.map((t) => `- ${t}`).join(`
|
|
|
25837
25921
|
}
|
|
25838
25922
|
return patternEntityIds;
|
|
25839
25923
|
}
|
|
25924
|
+
var CAUSAL_PATTERN_THRESHOLD = 3;
|
|
25925
|
+
async function detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId) {
|
|
25926
|
+
const patternIds = [];
|
|
25927
|
+
const errors3 = createdPairs.filter((p) => p.learning.type === "error");
|
|
25928
|
+
const solutions = createdPairs.filter((p) => p.learning.type === "solution");
|
|
25929
|
+
if (errors3.length === 0 || solutions.length === 0)
|
|
25930
|
+
return patternIds;
|
|
25931
|
+
for (const errorPair of errors3) {
|
|
25932
|
+
try {
|
|
25933
|
+
const similarErrors = await findSimilarEntities(client2, errorPair.learning.title, errorPair.learning.content, workspaceId, {
|
|
25934
|
+
projectId,
|
|
25935
|
+
limit: 20,
|
|
25936
|
+
minRrfScore: 0.03,
|
|
25937
|
+
excludeIds: createdPairs.map((p) => p.id),
|
|
25938
|
+
type: "error"
|
|
25939
|
+
});
|
|
25940
|
+
const resolvedErrors = [];
|
|
25941
|
+
for (const similar of similarErrors.slice(0, 10)) {
|
|
25942
|
+
try {
|
|
25943
|
+
const { outgoing } = await client2.getRelatedEntities(similar.id);
|
|
25944
|
+
const resolvedByRel = outgoing.find((r) => r.relation_type === "resolved_by");
|
|
25945
|
+
if (resolvedByRel) {
|
|
25946
|
+
resolvedErrors.push({
|
|
25947
|
+
errorId: similar.id,
|
|
25948
|
+
errorTitle: similar.title,
|
|
25949
|
+
solutionTitle: resolvedByRel.target_title || "unknown"
|
|
25950
|
+
});
|
|
25951
|
+
}
|
|
25952
|
+
} catch {}
|
|
25953
|
+
}
|
|
25954
|
+
if (resolvedErrors.length + 1 < CAUSAL_PATTERN_THRESHOLD)
|
|
25955
|
+
continue;
|
|
25956
|
+
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
25957
|
+
workspace_id: workspaceId,
|
|
25958
|
+
project_id: projectId,
|
|
25959
|
+
type: "pattern",
|
|
25960
|
+
limit: 10
|
|
25961
|
+
});
|
|
25962
|
+
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_chain_type === "error_resolved_by_solution");
|
|
25963
|
+
if (matchingPattern) {
|
|
25964
|
+
await client2.updateMemoryEntity(matchingPattern.id, {
|
|
25965
|
+
content: [
|
|
25966
|
+
`Recurring error→solution chain detected (${resolvedErrors.length + 1} instances).`,
|
|
25967
|
+
"",
|
|
25968
|
+
"## Error→Solution Pairs",
|
|
25969
|
+
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
25970
|
+
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`),
|
|
25971
|
+
"",
|
|
25972
|
+
`Last updated: ${new Date().toISOString()}`
|
|
25973
|
+
].join(`
|
|
25974
|
+
`),
|
|
25975
|
+
metadata: {
|
|
25976
|
+
pattern_chain_type: "error_resolved_by_solution",
|
|
25977
|
+
pattern_count: resolvedErrors.length + 1,
|
|
25978
|
+
last_updated: new Date().toISOString()
|
|
25979
|
+
}
|
|
25980
|
+
});
|
|
25981
|
+
for (const pair of [errorPair, solutions[0]]) {
|
|
25982
|
+
try {
|
|
25983
|
+
await client2.createMemoryRelation({
|
|
25984
|
+
source_id: pair.id,
|
|
25985
|
+
target_id: matchingPattern.id,
|
|
25986
|
+
relation_type: "part_of",
|
|
25987
|
+
confidence: 0.75
|
|
25988
|
+
});
|
|
25989
|
+
} catch {}
|
|
25990
|
+
}
|
|
25991
|
+
} else {
|
|
25992
|
+
const result = await client2.createMemoryEntity({
|
|
25993
|
+
workspace_id: workspaceId,
|
|
25994
|
+
project_id: projectId,
|
|
25995
|
+
type: "pattern",
|
|
25996
|
+
scope: "project",
|
|
25997
|
+
memory_tier: "reference",
|
|
25998
|
+
title: `Pattern: recurring error→solution chain (${resolvedErrors.length + 1} instances)`,
|
|
25999
|
+
content: [
|
|
26000
|
+
`Recurring error→solution chain detected across ${resolvedErrors.length + 1} sessions.`,
|
|
26001
|
+
"",
|
|
26002
|
+
"## Error→Solution Pairs",
|
|
26003
|
+
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
26004
|
+
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`)
|
|
26005
|
+
].join(`
|
|
26006
|
+
`),
|
|
26007
|
+
confidence: 0.8,
|
|
26008
|
+
tags: ["auto-extracted", "pattern", "causal-chain"],
|
|
26009
|
+
metadata: {
|
|
26010
|
+
source: "causal_pattern_detection",
|
|
26011
|
+
pattern_chain_type: "error_resolved_by_solution",
|
|
26012
|
+
pattern_count: resolvedErrors.length + 1
|
|
26013
|
+
},
|
|
26014
|
+
agent_identifier: session.agentIdentifier
|
|
26015
|
+
});
|
|
26016
|
+
const created = result.entity;
|
|
26017
|
+
if (created?.id) {
|
|
26018
|
+
patternIds.push(created.id);
|
|
26019
|
+
const memberIds = [
|
|
26020
|
+
errorPair.id,
|
|
26021
|
+
solutions[0].id,
|
|
26022
|
+
...resolvedErrors.slice(0, 4).map((r) => r.errorId)
|
|
26023
|
+
];
|
|
26024
|
+
for (const memberId of memberIds) {
|
|
26025
|
+
try {
|
|
26026
|
+
await client2.createMemoryRelation({
|
|
26027
|
+
source_id: memberId,
|
|
26028
|
+
target_id: created.id,
|
|
26029
|
+
relation_type: "part_of",
|
|
26030
|
+
confidence: 0.75
|
|
26031
|
+
});
|
|
26032
|
+
} catch {}
|
|
26033
|
+
}
|
|
26034
|
+
}
|
|
26035
|
+
}
|
|
26036
|
+
} catch {}
|
|
26037
|
+
}
|
|
26038
|
+
return patternIds;
|
|
26039
|
+
}
|
|
25840
26040
|
async function detectContradictions(client2, entityId, entityType, title, content, tags, workspaceId, projectId) {
|
|
25841
26041
|
if (!CONTRADICTION_TYPES.has(entityType))
|
|
25842
26042
|
return [];
|
|
@@ -25883,125 +26083,6 @@ async function detectContradictions(client2, entityId, entityType, title, conten
|
|
|
25883
26083
|
}
|
|
25884
26084
|
}
|
|
25885
26085
|
|
|
25886
|
-
// src/auto-session.ts
|
|
25887
|
-
var AUTO_START_TRIGGERS = new Set([
|
|
25888
|
-
"harmony_generate_prompt",
|
|
25889
|
-
"harmony_update_card",
|
|
25890
|
-
"harmony_move_card",
|
|
25891
|
-
"harmony_create_subtask",
|
|
25892
|
-
"harmony_toggle_subtask",
|
|
25893
|
-
"harmony_add_label_to_card",
|
|
25894
|
-
"harmony_remove_label_from_card"
|
|
25895
|
-
]);
|
|
25896
|
-
var INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
25897
|
-
var CHECK_INTERVAL_MS = 60 * 1000;
|
|
25898
|
-
var activeSessions = new Map;
|
|
25899
|
-
var inactivityTimer = null;
|
|
25900
|
-
var endCallback = null;
|
|
25901
|
-
var clientGetter = null;
|
|
25902
|
-
function initAutoSession(callback, getClient) {
|
|
25903
|
-
endCallback = callback;
|
|
25904
|
-
clientGetter = getClient;
|
|
25905
|
-
if (inactivityTimer)
|
|
25906
|
-
clearInterval(inactivityTimer);
|
|
25907
|
-
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
25908
|
-
}
|
|
25909
|
-
async function trackActivity(cardId, options) {
|
|
25910
|
-
const now = Date.now();
|
|
25911
|
-
const existing = activeSessions.get(cardId);
|
|
25912
|
-
if (existing) {
|
|
25913
|
-
existing.lastActivityAt = now;
|
|
25914
|
-
return;
|
|
25915
|
-
}
|
|
25916
|
-
if (!options?.autoStart)
|
|
25917
|
-
return;
|
|
25918
|
-
const client2 = options?.client ?? clientGetter?.();
|
|
25919
|
-
if (!client2)
|
|
25920
|
-
return;
|
|
25921
|
-
const toEnd = [];
|
|
25922
|
-
for (const [otherCardId, session] of activeSessions) {
|
|
25923
|
-
if (otherCardId !== cardId && !session.isExplicit) {
|
|
25924
|
-
toEnd.push(otherCardId);
|
|
25925
|
-
}
|
|
25926
|
-
}
|
|
25927
|
-
for (const otherCardId of toEnd) {
|
|
25928
|
-
await autoEndSession(client2, otherCardId, "completed");
|
|
25929
|
-
}
|
|
25930
|
-
try {
|
|
25931
|
-
await client2.startAgentSession(cardId, {
|
|
25932
|
-
agentIdentifier: "auto",
|
|
25933
|
-
agentName: "Auto-detected Agent",
|
|
25934
|
-
status: "working"
|
|
25935
|
-
});
|
|
25936
|
-
} catch {}
|
|
25937
|
-
activeSessions.set(cardId, {
|
|
25938
|
-
cardId,
|
|
25939
|
-
startedAt: now,
|
|
25940
|
-
lastActivityAt: now,
|
|
25941
|
-
isExplicit: false,
|
|
25942
|
-
agentIdentifier: "auto",
|
|
25943
|
-
agentName: "Auto-detected Agent"
|
|
25944
|
-
});
|
|
25945
|
-
}
|
|
25946
|
-
function markExplicit(cardId) {
|
|
25947
|
-
const existing = activeSessions.get(cardId);
|
|
25948
|
-
if (existing) {
|
|
25949
|
-
existing.isExplicit = true;
|
|
25950
|
-
} else {
|
|
25951
|
-
activeSessions.set(cardId, {
|
|
25952
|
-
cardId,
|
|
25953
|
-
startedAt: Date.now(),
|
|
25954
|
-
lastActivityAt: Date.now(),
|
|
25955
|
-
isExplicit: true,
|
|
25956
|
-
agentIdentifier: "explicit",
|
|
25957
|
-
agentName: "Explicit Agent"
|
|
25958
|
-
});
|
|
25959
|
-
}
|
|
25960
|
-
}
|
|
25961
|
-
function untrack(cardId) {
|
|
25962
|
-
activeSessions.delete(cardId);
|
|
25963
|
-
}
|
|
25964
|
-
async function shutdownAllSessions() {
|
|
25965
|
-
const client2 = clientGetter?.();
|
|
25966
|
-
if (!client2)
|
|
25967
|
-
return;
|
|
25968
|
-
const cardIds = [...activeSessions.keys()];
|
|
25969
|
-
const promises = cardIds.map((cardId) => autoEndSession(client2, cardId, "paused"));
|
|
25970
|
-
await Promise.allSettled(promises);
|
|
25971
|
-
}
|
|
25972
|
-
function destroyAutoSession() {
|
|
25973
|
-
if (inactivityTimer) {
|
|
25974
|
-
clearInterval(inactivityTimer);
|
|
25975
|
-
inactivityTimer = null;
|
|
25976
|
-
}
|
|
25977
|
-
activeSessions.clear();
|
|
25978
|
-
endCallback = null;
|
|
25979
|
-
clientGetter = null;
|
|
25980
|
-
}
|
|
25981
|
-
function checkInactivity() {
|
|
25982
|
-
const now = Date.now();
|
|
25983
|
-
const client2 = clientGetter?.();
|
|
25984
|
-
if (!client2)
|
|
25985
|
-
return;
|
|
25986
|
-
const entries = [...activeSessions.entries()];
|
|
25987
|
-
for (const [cardId, session] of entries) {
|
|
25988
|
-
if (session.isExplicit)
|
|
25989
|
-
continue;
|
|
25990
|
-
if (now - session.lastActivityAt > INACTIVITY_TIMEOUT_MS) {
|
|
25991
|
-
autoEndSession(client2, cardId, "completed").catch(() => {});
|
|
25992
|
-
}
|
|
25993
|
-
}
|
|
25994
|
-
}
|
|
25995
|
-
async function autoEndSession(client2, cardId, status) {
|
|
25996
|
-
activeSessions.delete(cardId);
|
|
25997
|
-
try {
|
|
25998
|
-
await client2.endAgentSession(cardId, { status });
|
|
25999
|
-
} catch {}
|
|
26000
|
-
try {
|
|
26001
|
-
await endCallback?.(client2, cardId, status);
|
|
26002
|
-
} catch {}
|
|
26003
|
-
}
|
|
26004
|
-
|
|
26005
26086
|
// src/api-client.ts
|
|
26006
26087
|
var RETRY_CONFIG = {
|
|
26007
26088
|
maxRetries: 3,
|
|
@@ -26302,6 +26383,22 @@ class HarmonyApiClient {
|
|
|
26302
26383
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
26303
26384
|
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
26304
26385
|
}
|
|
26386
|
+
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
26387
|
+
const params = new URLSearchParams({
|
|
26388
|
+
workspace_id: workspaceId,
|
|
26389
|
+
agent_identifier: agentIdentifier
|
|
26390
|
+
});
|
|
26391
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
26392
|
+
}
|
|
26393
|
+
async listAgentProfiles(workspaceId) {
|
|
26394
|
+
const params = new URLSearchParams({ workspace_id: workspaceId });
|
|
26395
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
26396
|
+
}
|
|
26397
|
+
async refreshAgentProfiles(workspaceId) {
|
|
26398
|
+
return this.request("POST", "/agent-profiles/refresh", {
|
|
26399
|
+
workspace_id: workspaceId
|
|
26400
|
+
});
|
|
26401
|
+
}
|
|
26305
26402
|
async createMemoryEntity(data) {
|
|
26306
26403
|
return this.request("POST", "/memory/entities", data);
|
|
26307
26404
|
}
|
|
@@ -26505,6 +26602,125 @@ function resetClient() {
|
|
|
26505
26602
|
client2 = null;
|
|
26506
26603
|
}
|
|
26507
26604
|
|
|
26605
|
+
// src/auto-session.ts
|
|
26606
|
+
var AUTO_START_TRIGGERS = new Set([
|
|
26607
|
+
"harmony_generate_prompt",
|
|
26608
|
+
"harmony_update_card",
|
|
26609
|
+
"harmony_move_card",
|
|
26610
|
+
"harmony_create_subtask",
|
|
26611
|
+
"harmony_toggle_subtask",
|
|
26612
|
+
"harmony_add_label_to_card",
|
|
26613
|
+
"harmony_remove_label_from_card"
|
|
26614
|
+
]);
|
|
26615
|
+
var INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
26616
|
+
var CHECK_INTERVAL_MS = 60 * 1000;
|
|
26617
|
+
var activeSessions = new Map;
|
|
26618
|
+
var inactivityTimer = null;
|
|
26619
|
+
var endCallback = null;
|
|
26620
|
+
var clientGetter = null;
|
|
26621
|
+
function initAutoSession(callback, getClient2) {
|
|
26622
|
+
endCallback = callback;
|
|
26623
|
+
clientGetter = getClient2;
|
|
26624
|
+
if (inactivityTimer)
|
|
26625
|
+
clearInterval(inactivityTimer);
|
|
26626
|
+
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
26627
|
+
}
|
|
26628
|
+
async function trackActivity(cardId, options) {
|
|
26629
|
+
const now = Date.now();
|
|
26630
|
+
const existing = activeSessions.get(cardId);
|
|
26631
|
+
if (existing) {
|
|
26632
|
+
existing.lastActivityAt = now;
|
|
26633
|
+
return;
|
|
26634
|
+
}
|
|
26635
|
+
if (!options?.autoStart)
|
|
26636
|
+
return;
|
|
26637
|
+
const client3 = options?.client ?? clientGetter?.();
|
|
26638
|
+
if (!client3)
|
|
26639
|
+
return;
|
|
26640
|
+
const toEnd = [];
|
|
26641
|
+
for (const [otherCardId, session] of activeSessions) {
|
|
26642
|
+
if (otherCardId !== cardId && !session.isExplicit) {
|
|
26643
|
+
toEnd.push(otherCardId);
|
|
26644
|
+
}
|
|
26645
|
+
}
|
|
26646
|
+
for (const otherCardId of toEnd) {
|
|
26647
|
+
await autoEndSession(client3, otherCardId, "completed");
|
|
26648
|
+
}
|
|
26649
|
+
try {
|
|
26650
|
+
await client3.startAgentSession(cardId, {
|
|
26651
|
+
agentIdentifier: "auto",
|
|
26652
|
+
agentName: "Auto-detected Agent",
|
|
26653
|
+
status: "working"
|
|
26654
|
+
});
|
|
26655
|
+
} catch {}
|
|
26656
|
+
activeSessions.set(cardId, {
|
|
26657
|
+
cardId,
|
|
26658
|
+
startedAt: now,
|
|
26659
|
+
lastActivityAt: now,
|
|
26660
|
+
isExplicit: false,
|
|
26661
|
+
agentIdentifier: "auto",
|
|
26662
|
+
agentName: "Auto-detected Agent"
|
|
26663
|
+
});
|
|
26664
|
+
}
|
|
26665
|
+
function markExplicit(cardId) {
|
|
26666
|
+
const existing = activeSessions.get(cardId);
|
|
26667
|
+
if (existing) {
|
|
26668
|
+
existing.isExplicit = true;
|
|
26669
|
+
} else {
|
|
26670
|
+
activeSessions.set(cardId, {
|
|
26671
|
+
cardId,
|
|
26672
|
+
startedAt: Date.now(),
|
|
26673
|
+
lastActivityAt: Date.now(),
|
|
26674
|
+
isExplicit: true,
|
|
26675
|
+
agentIdentifier: "explicit",
|
|
26676
|
+
agentName: "Explicit Agent"
|
|
26677
|
+
});
|
|
26678
|
+
}
|
|
26679
|
+
}
|
|
26680
|
+
function untrack(cardId) {
|
|
26681
|
+
activeSessions.delete(cardId);
|
|
26682
|
+
}
|
|
26683
|
+
async function shutdownAllSessions() {
|
|
26684
|
+
const client3 = clientGetter?.();
|
|
26685
|
+
if (!client3)
|
|
26686
|
+
return;
|
|
26687
|
+
const cardIds = [...activeSessions.keys()];
|
|
26688
|
+
const promises = cardIds.map((cardId) => autoEndSession(client3, cardId, "paused"));
|
|
26689
|
+
await Promise.allSettled(promises);
|
|
26690
|
+
}
|
|
26691
|
+
function destroyAutoSession() {
|
|
26692
|
+
if (inactivityTimer) {
|
|
26693
|
+
clearInterval(inactivityTimer);
|
|
26694
|
+
inactivityTimer = null;
|
|
26695
|
+
}
|
|
26696
|
+
activeSessions.clear();
|
|
26697
|
+
endCallback = null;
|
|
26698
|
+
clientGetter = null;
|
|
26699
|
+
}
|
|
26700
|
+
function checkInactivity() {
|
|
26701
|
+
const now = Date.now();
|
|
26702
|
+
const client3 = clientGetter?.();
|
|
26703
|
+
if (!client3)
|
|
26704
|
+
return;
|
|
26705
|
+
const entries = [...activeSessions.entries()];
|
|
26706
|
+
for (const [cardId, session] of entries) {
|
|
26707
|
+
if (session.isExplicit)
|
|
26708
|
+
continue;
|
|
26709
|
+
if (now - session.lastActivityAt > INACTIVITY_TIMEOUT_MS) {
|
|
26710
|
+
autoEndSession(client3, cardId, "completed").catch(() => {});
|
|
26711
|
+
}
|
|
26712
|
+
}
|
|
26713
|
+
}
|
|
26714
|
+
async function autoEndSession(client3, cardId, status) {
|
|
26715
|
+
activeSessions.delete(cardId);
|
|
26716
|
+
try {
|
|
26717
|
+
await client3.endAgentSession(cardId, { status });
|
|
26718
|
+
} catch {}
|
|
26719
|
+
try {
|
|
26720
|
+
await endCallback?.(client3, cardId, status);
|
|
26721
|
+
} catch {}
|
|
26722
|
+
}
|
|
26723
|
+
|
|
26508
26724
|
// src/consolidation.ts
|
|
26509
26725
|
async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
26510
26726
|
const dryRun = options?.dryRun !== false;
|
|
@@ -28166,6 +28382,22 @@ var TOOLS = {
|
|
|
28166
28382
|
required: ["cardId"]
|
|
28167
28383
|
}
|
|
28168
28384
|
},
|
|
28385
|
+
harmony_get_agent_profile: {
|
|
28386
|
+
description: "Get aggregate performance profile for an agent. Shows total sessions, completion rate, average duration, and more. Defaults to the current agent if no identifier provided.",
|
|
28387
|
+
inputSchema: {
|
|
28388
|
+
type: "object",
|
|
28389
|
+
properties: {
|
|
28390
|
+
agentIdentifier: {
|
|
28391
|
+
type: "string",
|
|
28392
|
+
description: "Agent identifier (e.g., 'claude-code'). Defaults to current agent."
|
|
28393
|
+
},
|
|
28394
|
+
workspaceId: {
|
|
28395
|
+
type: "string",
|
|
28396
|
+
description: "Workspace ID (optional if context set)"
|
|
28397
|
+
}
|
|
28398
|
+
}
|
|
28399
|
+
}
|
|
28400
|
+
},
|
|
28169
28401
|
harmony_generate_prompt: {
|
|
28170
28402
|
description: "Generate an AI-ready prompt from a card. Automatically infers role and focus based on labels (bug, feature, design, etc.). Use this to create context-rich prompts for working on cards.",
|
|
28171
28403
|
inputSchema: {
|
|
@@ -29023,6 +29255,62 @@ async function runEndSessionPipeline(client3, deps, cardId, sessionStatus, endPr
|
|
|
29023
29255
|
const learningResult = await extractLearnings(client3, sessionContext);
|
|
29024
29256
|
learningsExtracted = learningResult.count;
|
|
29025
29257
|
} catch {}
|
|
29258
|
+
try {
|
|
29259
|
+
const workspaceId = deps.getActiveWorkspaceId();
|
|
29260
|
+
const agentId = sessionData?.agent_identifier || "unknown";
|
|
29261
|
+
if (workspaceId && agentId !== "unknown") {
|
|
29262
|
+
(async () => {
|
|
29263
|
+
try {
|
|
29264
|
+
await client3.refreshAgentProfiles(workspaceId);
|
|
29265
|
+
const { profile } = await client3.getAgentProfile(workspaceId, agentId);
|
|
29266
|
+
if (profile) {
|
|
29267
|
+
const p = profile;
|
|
29268
|
+
const title = `Agent Profile: ${agentId}`;
|
|
29269
|
+
const content = [
|
|
29270
|
+
`## ${agentId} Performance Profile`,
|
|
29271
|
+
"",
|
|
29272
|
+
`- **Total sessions:** ${p.total_sessions}`,
|
|
29273
|
+
`- **Completed:** ${p.completed_sessions} (${p.completion_rate_pct}%)`,
|
|
29274
|
+
`- **Paused:** ${p.paused_sessions}`,
|
|
29275
|
+
`- **Blocked:** ${p.blocked_sessions}`,
|
|
29276
|
+
`- **Avg duration:** ${Math.round(Number(p.avg_active_duration_ms || 0) / 1000)}s`,
|
|
29277
|
+
`- **Avg progress:** ${p.avg_completion_progress}%`,
|
|
29278
|
+
`- **First session:** ${p.first_session_at}`,
|
|
29279
|
+
`- **Last session:** ${p.last_session_at}`
|
|
29280
|
+
].join(`
|
|
29281
|
+
`);
|
|
29282
|
+
const existing = await client3.listMemoryEntities({
|
|
29283
|
+
workspace_id: workspaceId,
|
|
29284
|
+
type: "agent",
|
|
29285
|
+
limit: 50
|
|
29286
|
+
});
|
|
29287
|
+
const entities = existing.entities || [];
|
|
29288
|
+
const match = entities.find((e) => e.title === title || e.agent_identifier === agentId);
|
|
29289
|
+
if (match) {
|
|
29290
|
+
await client3.updateMemoryEntity(match.id, {
|
|
29291
|
+
content,
|
|
29292
|
+
confidence: 1,
|
|
29293
|
+
metadata: p
|
|
29294
|
+
});
|
|
29295
|
+
} else {
|
|
29296
|
+
await client3.createMemoryEntity({
|
|
29297
|
+
workspace_id: workspaceId,
|
|
29298
|
+
type: "agent",
|
|
29299
|
+
scope: "workspace",
|
|
29300
|
+
memory_tier: "reference",
|
|
29301
|
+
title,
|
|
29302
|
+
content,
|
|
29303
|
+
confidence: 1,
|
|
29304
|
+
tags: ["agent-profile", agentId],
|
|
29305
|
+
agent_identifier: agentId,
|
|
29306
|
+
metadata: p
|
|
29307
|
+
});
|
|
29308
|
+
}
|
|
29309
|
+
}
|
|
29310
|
+
} catch {}
|
|
29311
|
+
})();
|
|
29312
|
+
}
|
|
29313
|
+
} catch {}
|
|
29026
29314
|
try {
|
|
29027
29315
|
const feedbackResult = await recordContextFeedback(client3, cardId, sessionStatus, endProgressPercent, (sessionData?.blockers?.length ?? 0) > 0);
|
|
29028
29316
|
feedbackAdjusted = feedbackResult.adjusted;
|
|
@@ -29517,18 +29805,18 @@ async function handleToolCall(name, args, deps) {
|
|
|
29517
29805
|
});
|
|
29518
29806
|
untrack(cardId);
|
|
29519
29807
|
let movedTo = null;
|
|
29520
|
-
|
|
29521
|
-
let
|
|
29522
|
-
let
|
|
29523
|
-
let
|
|
29524
|
-
let
|
|
29808
|
+
const _learningsExtracted = 0;
|
|
29809
|
+
let _cardTitle = "";
|
|
29810
|
+
let _cardLabels = [];
|
|
29811
|
+
let _cardDescription = "";
|
|
29812
|
+
let _cardSubtasks = [];
|
|
29525
29813
|
try {
|
|
29526
29814
|
const { card } = await client3.getCard(cardId);
|
|
29527
29815
|
const typedCard = card;
|
|
29528
|
-
|
|
29529
|
-
|
|
29530
|
-
|
|
29531
|
-
|
|
29816
|
+
_cardTitle = typedCard.title || "";
|
|
29817
|
+
_cardLabels = (typedCard.labels || []).map((l) => l.name);
|
|
29818
|
+
_cardDescription = typedCard.description || "";
|
|
29819
|
+
_cardSubtasks = (typedCard.subtasks || []).map((s) => ({
|
|
29532
29820
|
title: s.title,
|
|
29533
29821
|
done: s.done
|
|
29534
29822
|
}));
|
|
@@ -29571,6 +29859,18 @@ async function handleToolCall(name, args, deps) {
|
|
|
29571
29859
|
});
|
|
29572
29860
|
return { success: true, ...result };
|
|
29573
29861
|
}
|
|
29862
|
+
case "harmony_get_agent_profile": {
|
|
29863
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29864
|
+
if (!workspaceId) {
|
|
29865
|
+
return {
|
|
29866
|
+
success: false,
|
|
29867
|
+
error: "No workspace context. Provide workspaceId or set context."
|
|
29868
|
+
};
|
|
29869
|
+
}
|
|
29870
|
+
const agentIdentifier = args.agentIdentifier || "claude-code";
|
|
29871
|
+
const result = await client3.getAgentProfile(workspaceId, agentIdentifier);
|
|
29872
|
+
return { success: true, ...result };
|
|
29873
|
+
}
|
|
29574
29874
|
case "harmony_generate_prompt": {
|
|
29575
29875
|
let cardData;
|
|
29576
29876
|
let columnData = null;
|
package/dist/index.js
CHANGED
|
@@ -23075,7 +23075,8 @@ async function findSimilarEntities(client2, title, content, workspaceId, options
|
|
|
23075
23075
|
try {
|
|
23076
23076
|
const { entities } = await client2.searchMemoryEntities(workspaceId, query, {
|
|
23077
23077
|
project_id: options?.projectId,
|
|
23078
|
-
limit: options?.limit ?? 20
|
|
23078
|
+
limit: options?.limit ?? 20,
|
|
23079
|
+
type: options?.type
|
|
23079
23080
|
});
|
|
23080
23081
|
const minScore = options?.minRrfScore ?? 0;
|
|
23081
23082
|
const excludeSet = new Set(options?.excludeIds || []);
|
|
@@ -23090,6 +23091,57 @@ async function findSimilarEntities(client2, title, content, workspaceId, options
|
|
|
23090
23091
|
return [];
|
|
23091
23092
|
}
|
|
23092
23093
|
}
|
|
23094
|
+
var CAUSAL_LOOKUP = [
|
|
23095
|
+
{
|
|
23096
|
+
sourceType: "error",
|
|
23097
|
+
targetType: "solution",
|
|
23098
|
+
relation: "resolved_by",
|
|
23099
|
+
direction: "forward"
|
|
23100
|
+
},
|
|
23101
|
+
{
|
|
23102
|
+
sourceType: "solution",
|
|
23103
|
+
targetType: "error",
|
|
23104
|
+
relation: "resolved_by",
|
|
23105
|
+
direction: "reverse"
|
|
23106
|
+
},
|
|
23107
|
+
{
|
|
23108
|
+
sourceType: "lesson",
|
|
23109
|
+
targetType: "error",
|
|
23110
|
+
relation: "learned_from",
|
|
23111
|
+
direction: "forward"
|
|
23112
|
+
}
|
|
23113
|
+
];
|
|
23114
|
+
async function linkCrossTypeNeighbors(client2, entityId, entityType, title, content, workspaceId, projectId) {
|
|
23115
|
+
const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
|
|
23116
|
+
if (rules.length === 0)
|
|
23117
|
+
return { relationsCreated: 0 };
|
|
23118
|
+
let relationsCreated = 0;
|
|
23119
|
+
for (const rule of rules) {
|
|
23120
|
+
try {
|
|
23121
|
+
const matches = await findSimilarEntities(client2, title, content, workspaceId, {
|
|
23122
|
+
projectId,
|
|
23123
|
+
limit: 10,
|
|
23124
|
+
minRrfScore: 0.04,
|
|
23125
|
+
excludeIds: [entityId],
|
|
23126
|
+
type: rule.targetType
|
|
23127
|
+
});
|
|
23128
|
+
for (const match of matches.slice(0, 3)) {
|
|
23129
|
+
const sourceId = rule.direction === "forward" ? entityId : match.id;
|
|
23130
|
+
const targetId = rule.direction === "forward" ? match.id : entityId;
|
|
23131
|
+
try {
|
|
23132
|
+
await client2.createMemoryRelation({
|
|
23133
|
+
source_id: sourceId,
|
|
23134
|
+
target_id: targetId,
|
|
23135
|
+
relation_type: rule.relation,
|
|
23136
|
+
confidence: 0.65
|
|
23137
|
+
});
|
|
23138
|
+
relationsCreated++;
|
|
23139
|
+
} catch {}
|
|
23140
|
+
}
|
|
23141
|
+
} catch {}
|
|
23142
|
+
}
|
|
23143
|
+
return { relationsCreated };
|
|
23144
|
+
}
|
|
23093
23145
|
|
|
23094
23146
|
// src/active-learning.ts
|
|
23095
23147
|
var CONTRADICTION_TYPES = new Set([
|
|
@@ -23362,6 +23414,31 @@ Agent: ${session.agentName} | ${new Date().toISOString().split("T")[0]}`;
|
|
|
23362
23414
|
}
|
|
23363
23415
|
};
|
|
23364
23416
|
}
|
|
23417
|
+
var SESSION_CAUSAL_RULES = [
|
|
23418
|
+
{ sourceType: "error", targetType: "solution", relation: "resolved_by", confidence: 0.8 },
|
|
23419
|
+
{ sourceType: "lesson", targetType: "error", relation: "learned_from", confidence: 0.75 }
|
|
23420
|
+
];
|
|
23421
|
+
async function linkSessionEntities(client2, createdPairs, _workspaceId, _projectId) {
|
|
23422
|
+
let relationsCreated = 0;
|
|
23423
|
+
for (const rule of SESSION_CAUSAL_RULES) {
|
|
23424
|
+
const sources = createdPairs.filter((p) => p.learning.type === rule.sourceType);
|
|
23425
|
+
const targets = createdPairs.filter((p) => p.learning.type === rule.targetType);
|
|
23426
|
+
for (const source of sources) {
|
|
23427
|
+
for (const target of targets) {
|
|
23428
|
+
try {
|
|
23429
|
+
await client2.createMemoryRelation({
|
|
23430
|
+
source_id: source.id,
|
|
23431
|
+
target_id: target.id,
|
|
23432
|
+
relation_type: rule.relation,
|
|
23433
|
+
confidence: rule.confidence
|
|
23434
|
+
});
|
|
23435
|
+
relationsCreated++;
|
|
23436
|
+
} catch {}
|
|
23437
|
+
}
|
|
23438
|
+
}
|
|
23439
|
+
}
|
|
23440
|
+
return { relationsCreated };
|
|
23441
|
+
}
|
|
23365
23442
|
async function extractLearnings(client2, session) {
|
|
23366
23443
|
const workspaceId = getActiveWorkspaceId();
|
|
23367
23444
|
if (!workspaceId) {
|
|
@@ -23503,10 +23580,17 @@ Agent: ${session.agentName}`,
|
|
|
23503
23580
|
for (const { id, learning } of createdPairs) {
|
|
23504
23581
|
autoExpandGraph(client2, id, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
23505
23582
|
detectContradictions(client2, id, learning.type, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
23583
|
+
linkCrossTypeNeighbors(client2, id, learning.type, learning.title, learning.content, workspaceId, projectId).catch(() => {});
|
|
23584
|
+
}
|
|
23585
|
+
if (createdPairs.length >= 2) {
|
|
23586
|
+
linkSessionEntities(client2, createdPairs, workspaceId, projectId).catch(() => {});
|
|
23506
23587
|
}
|
|
23507
23588
|
if (entityIds.length > 0) {
|
|
23508
23589
|
detectAndCreatePatterns(client2, entityIds, session, workspaceId, projectId).catch(() => {});
|
|
23509
23590
|
}
|
|
23591
|
+
if (createdPairs.length > 0) {
|
|
23592
|
+
detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId).catch(() => {});
|
|
23593
|
+
}
|
|
23510
23594
|
clearMidSessionTracking(session.cardId);
|
|
23511
23595
|
return { count: entityIds.length, entityIds };
|
|
23512
23596
|
}
|
|
@@ -23597,6 +23681,122 @@ ${memberTitles.map((t) => `- ${t}`).join(`
|
|
|
23597
23681
|
}
|
|
23598
23682
|
return patternEntityIds;
|
|
23599
23683
|
}
|
|
23684
|
+
var CAUSAL_PATTERN_THRESHOLD = 3;
|
|
23685
|
+
async function detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId) {
|
|
23686
|
+
const patternIds = [];
|
|
23687
|
+
const errors3 = createdPairs.filter((p) => p.learning.type === "error");
|
|
23688
|
+
const solutions = createdPairs.filter((p) => p.learning.type === "solution");
|
|
23689
|
+
if (errors3.length === 0 || solutions.length === 0)
|
|
23690
|
+
return patternIds;
|
|
23691
|
+
for (const errorPair of errors3) {
|
|
23692
|
+
try {
|
|
23693
|
+
const similarErrors = await findSimilarEntities(client2, errorPair.learning.title, errorPair.learning.content, workspaceId, {
|
|
23694
|
+
projectId,
|
|
23695
|
+
limit: 20,
|
|
23696
|
+
minRrfScore: 0.03,
|
|
23697
|
+
excludeIds: createdPairs.map((p) => p.id),
|
|
23698
|
+
type: "error"
|
|
23699
|
+
});
|
|
23700
|
+
const resolvedErrors = [];
|
|
23701
|
+
for (const similar of similarErrors.slice(0, 10)) {
|
|
23702
|
+
try {
|
|
23703
|
+
const { outgoing } = await client2.getRelatedEntities(similar.id);
|
|
23704
|
+
const resolvedByRel = outgoing.find((r) => r.relation_type === "resolved_by");
|
|
23705
|
+
if (resolvedByRel) {
|
|
23706
|
+
resolvedErrors.push({
|
|
23707
|
+
errorId: similar.id,
|
|
23708
|
+
errorTitle: similar.title,
|
|
23709
|
+
solutionTitle: resolvedByRel.target_title || "unknown"
|
|
23710
|
+
});
|
|
23711
|
+
}
|
|
23712
|
+
} catch {}
|
|
23713
|
+
}
|
|
23714
|
+
if (resolvedErrors.length + 1 < CAUSAL_PATTERN_THRESHOLD)
|
|
23715
|
+
continue;
|
|
23716
|
+
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
23717
|
+
workspace_id: workspaceId,
|
|
23718
|
+
project_id: projectId,
|
|
23719
|
+
type: "pattern",
|
|
23720
|
+
limit: 10
|
|
23721
|
+
});
|
|
23722
|
+
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_chain_type === "error_resolved_by_solution");
|
|
23723
|
+
if (matchingPattern) {
|
|
23724
|
+
await client2.updateMemoryEntity(matchingPattern.id, {
|
|
23725
|
+
content: [
|
|
23726
|
+
`Recurring error→solution chain detected (${resolvedErrors.length + 1} instances).`,
|
|
23727
|
+
"",
|
|
23728
|
+
"## Error→Solution Pairs",
|
|
23729
|
+
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
23730
|
+
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`),
|
|
23731
|
+
"",
|
|
23732
|
+
`Last updated: ${new Date().toISOString()}`
|
|
23733
|
+
].join(`
|
|
23734
|
+
`),
|
|
23735
|
+
metadata: {
|
|
23736
|
+
pattern_chain_type: "error_resolved_by_solution",
|
|
23737
|
+
pattern_count: resolvedErrors.length + 1,
|
|
23738
|
+
last_updated: new Date().toISOString()
|
|
23739
|
+
}
|
|
23740
|
+
});
|
|
23741
|
+
for (const pair of [errorPair, solutions[0]]) {
|
|
23742
|
+
try {
|
|
23743
|
+
await client2.createMemoryRelation({
|
|
23744
|
+
source_id: pair.id,
|
|
23745
|
+
target_id: matchingPattern.id,
|
|
23746
|
+
relation_type: "part_of",
|
|
23747
|
+
confidence: 0.75
|
|
23748
|
+
});
|
|
23749
|
+
} catch {}
|
|
23750
|
+
}
|
|
23751
|
+
} else {
|
|
23752
|
+
const result = await client2.createMemoryEntity({
|
|
23753
|
+
workspace_id: workspaceId,
|
|
23754
|
+
project_id: projectId,
|
|
23755
|
+
type: "pattern",
|
|
23756
|
+
scope: "project",
|
|
23757
|
+
memory_tier: "reference",
|
|
23758
|
+
title: `Pattern: recurring error→solution chain (${resolvedErrors.length + 1} instances)`,
|
|
23759
|
+
content: [
|
|
23760
|
+
`Recurring error→solution chain detected across ${resolvedErrors.length + 1} sessions.`,
|
|
23761
|
+
"",
|
|
23762
|
+
"## Error→Solution Pairs",
|
|
23763
|
+
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
23764
|
+
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`)
|
|
23765
|
+
].join(`
|
|
23766
|
+
`),
|
|
23767
|
+
confidence: 0.8,
|
|
23768
|
+
tags: ["auto-extracted", "pattern", "causal-chain"],
|
|
23769
|
+
metadata: {
|
|
23770
|
+
source: "causal_pattern_detection",
|
|
23771
|
+
pattern_chain_type: "error_resolved_by_solution",
|
|
23772
|
+
pattern_count: resolvedErrors.length + 1
|
|
23773
|
+
},
|
|
23774
|
+
agent_identifier: session.agentIdentifier
|
|
23775
|
+
});
|
|
23776
|
+
const created = result.entity;
|
|
23777
|
+
if (created?.id) {
|
|
23778
|
+
patternIds.push(created.id);
|
|
23779
|
+
const memberIds = [
|
|
23780
|
+
errorPair.id,
|
|
23781
|
+
solutions[0].id,
|
|
23782
|
+
...resolvedErrors.slice(0, 4).map((r) => r.errorId)
|
|
23783
|
+
];
|
|
23784
|
+
for (const memberId of memberIds) {
|
|
23785
|
+
try {
|
|
23786
|
+
await client2.createMemoryRelation({
|
|
23787
|
+
source_id: memberId,
|
|
23788
|
+
target_id: created.id,
|
|
23789
|
+
relation_type: "part_of",
|
|
23790
|
+
confidence: 0.75
|
|
23791
|
+
});
|
|
23792
|
+
} catch {}
|
|
23793
|
+
}
|
|
23794
|
+
}
|
|
23795
|
+
}
|
|
23796
|
+
} catch {}
|
|
23797
|
+
}
|
|
23798
|
+
return patternIds;
|
|
23799
|
+
}
|
|
23600
23800
|
async function detectContradictions(client2, entityId, entityType, title, content, tags, workspaceId, projectId) {
|
|
23601
23801
|
if (!CONTRADICTION_TYPES.has(entityType))
|
|
23602
23802
|
return [];
|
|
@@ -23643,125 +23843,6 @@ async function detectContradictions(client2, entityId, entityType, title, conten
|
|
|
23643
23843
|
}
|
|
23644
23844
|
}
|
|
23645
23845
|
|
|
23646
|
-
// src/auto-session.ts
|
|
23647
|
-
var AUTO_START_TRIGGERS = new Set([
|
|
23648
|
-
"harmony_generate_prompt",
|
|
23649
|
-
"harmony_update_card",
|
|
23650
|
-
"harmony_move_card",
|
|
23651
|
-
"harmony_create_subtask",
|
|
23652
|
-
"harmony_toggle_subtask",
|
|
23653
|
-
"harmony_add_label_to_card",
|
|
23654
|
-
"harmony_remove_label_from_card"
|
|
23655
|
-
]);
|
|
23656
|
-
var INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
23657
|
-
var CHECK_INTERVAL_MS = 60 * 1000;
|
|
23658
|
-
var activeSessions = new Map;
|
|
23659
|
-
var inactivityTimer = null;
|
|
23660
|
-
var endCallback = null;
|
|
23661
|
-
var clientGetter = null;
|
|
23662
|
-
function initAutoSession(callback, getClient) {
|
|
23663
|
-
endCallback = callback;
|
|
23664
|
-
clientGetter = getClient;
|
|
23665
|
-
if (inactivityTimer)
|
|
23666
|
-
clearInterval(inactivityTimer);
|
|
23667
|
-
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
23668
|
-
}
|
|
23669
|
-
async function trackActivity(cardId, options) {
|
|
23670
|
-
const now = Date.now();
|
|
23671
|
-
const existing = activeSessions.get(cardId);
|
|
23672
|
-
if (existing) {
|
|
23673
|
-
existing.lastActivityAt = now;
|
|
23674
|
-
return;
|
|
23675
|
-
}
|
|
23676
|
-
if (!options?.autoStart)
|
|
23677
|
-
return;
|
|
23678
|
-
const client2 = options?.client ?? clientGetter?.();
|
|
23679
|
-
if (!client2)
|
|
23680
|
-
return;
|
|
23681
|
-
const toEnd = [];
|
|
23682
|
-
for (const [otherCardId, session] of activeSessions) {
|
|
23683
|
-
if (otherCardId !== cardId && !session.isExplicit) {
|
|
23684
|
-
toEnd.push(otherCardId);
|
|
23685
|
-
}
|
|
23686
|
-
}
|
|
23687
|
-
for (const otherCardId of toEnd) {
|
|
23688
|
-
await autoEndSession(client2, otherCardId, "completed");
|
|
23689
|
-
}
|
|
23690
|
-
try {
|
|
23691
|
-
await client2.startAgentSession(cardId, {
|
|
23692
|
-
agentIdentifier: "auto",
|
|
23693
|
-
agentName: "Auto-detected Agent",
|
|
23694
|
-
status: "working"
|
|
23695
|
-
});
|
|
23696
|
-
} catch {}
|
|
23697
|
-
activeSessions.set(cardId, {
|
|
23698
|
-
cardId,
|
|
23699
|
-
startedAt: now,
|
|
23700
|
-
lastActivityAt: now,
|
|
23701
|
-
isExplicit: false,
|
|
23702
|
-
agentIdentifier: "auto",
|
|
23703
|
-
agentName: "Auto-detected Agent"
|
|
23704
|
-
});
|
|
23705
|
-
}
|
|
23706
|
-
function markExplicit(cardId) {
|
|
23707
|
-
const existing = activeSessions.get(cardId);
|
|
23708
|
-
if (existing) {
|
|
23709
|
-
existing.isExplicit = true;
|
|
23710
|
-
} else {
|
|
23711
|
-
activeSessions.set(cardId, {
|
|
23712
|
-
cardId,
|
|
23713
|
-
startedAt: Date.now(),
|
|
23714
|
-
lastActivityAt: Date.now(),
|
|
23715
|
-
isExplicit: true,
|
|
23716
|
-
agentIdentifier: "explicit",
|
|
23717
|
-
agentName: "Explicit Agent"
|
|
23718
|
-
});
|
|
23719
|
-
}
|
|
23720
|
-
}
|
|
23721
|
-
function untrack(cardId) {
|
|
23722
|
-
activeSessions.delete(cardId);
|
|
23723
|
-
}
|
|
23724
|
-
async function shutdownAllSessions() {
|
|
23725
|
-
const client2 = clientGetter?.();
|
|
23726
|
-
if (!client2)
|
|
23727
|
-
return;
|
|
23728
|
-
const cardIds = [...activeSessions.keys()];
|
|
23729
|
-
const promises = cardIds.map((cardId) => autoEndSession(client2, cardId, "paused"));
|
|
23730
|
-
await Promise.allSettled(promises);
|
|
23731
|
-
}
|
|
23732
|
-
function destroyAutoSession() {
|
|
23733
|
-
if (inactivityTimer) {
|
|
23734
|
-
clearInterval(inactivityTimer);
|
|
23735
|
-
inactivityTimer = null;
|
|
23736
|
-
}
|
|
23737
|
-
activeSessions.clear();
|
|
23738
|
-
endCallback = null;
|
|
23739
|
-
clientGetter = null;
|
|
23740
|
-
}
|
|
23741
|
-
function checkInactivity() {
|
|
23742
|
-
const now = Date.now();
|
|
23743
|
-
const client2 = clientGetter?.();
|
|
23744
|
-
if (!client2)
|
|
23745
|
-
return;
|
|
23746
|
-
const entries = [...activeSessions.entries()];
|
|
23747
|
-
for (const [cardId, session] of entries) {
|
|
23748
|
-
if (session.isExplicit)
|
|
23749
|
-
continue;
|
|
23750
|
-
if (now - session.lastActivityAt > INACTIVITY_TIMEOUT_MS) {
|
|
23751
|
-
autoEndSession(client2, cardId, "completed").catch(() => {});
|
|
23752
|
-
}
|
|
23753
|
-
}
|
|
23754
|
-
}
|
|
23755
|
-
async function autoEndSession(client2, cardId, status) {
|
|
23756
|
-
activeSessions.delete(cardId);
|
|
23757
|
-
try {
|
|
23758
|
-
await client2.endAgentSession(cardId, { status });
|
|
23759
|
-
} catch {}
|
|
23760
|
-
try {
|
|
23761
|
-
await endCallback?.(client2, cardId, status);
|
|
23762
|
-
} catch {}
|
|
23763
|
-
}
|
|
23764
|
-
|
|
23765
23846
|
// src/api-client.ts
|
|
23766
23847
|
var RETRY_CONFIG = {
|
|
23767
23848
|
maxRetries: 3,
|
|
@@ -24062,6 +24143,22 @@ class HarmonyApiClient {
|
|
|
24062
24143
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
24063
24144
|
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
24064
24145
|
}
|
|
24146
|
+
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
24147
|
+
const params = new URLSearchParams({
|
|
24148
|
+
workspace_id: workspaceId,
|
|
24149
|
+
agent_identifier: agentIdentifier
|
|
24150
|
+
});
|
|
24151
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
24152
|
+
}
|
|
24153
|
+
async listAgentProfiles(workspaceId) {
|
|
24154
|
+
const params = new URLSearchParams({ workspace_id: workspaceId });
|
|
24155
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
24156
|
+
}
|
|
24157
|
+
async refreshAgentProfiles(workspaceId) {
|
|
24158
|
+
return this.request("POST", "/agent-profiles/refresh", {
|
|
24159
|
+
workspace_id: workspaceId
|
|
24160
|
+
});
|
|
24161
|
+
}
|
|
24065
24162
|
async createMemoryEntity(data) {
|
|
24066
24163
|
return this.request("POST", "/memory/entities", data);
|
|
24067
24164
|
}
|
|
@@ -24265,6 +24362,125 @@ function resetClient() {
|
|
|
24265
24362
|
client2 = null;
|
|
24266
24363
|
}
|
|
24267
24364
|
|
|
24365
|
+
// src/auto-session.ts
|
|
24366
|
+
var AUTO_START_TRIGGERS = new Set([
|
|
24367
|
+
"harmony_generate_prompt",
|
|
24368
|
+
"harmony_update_card",
|
|
24369
|
+
"harmony_move_card",
|
|
24370
|
+
"harmony_create_subtask",
|
|
24371
|
+
"harmony_toggle_subtask",
|
|
24372
|
+
"harmony_add_label_to_card",
|
|
24373
|
+
"harmony_remove_label_from_card"
|
|
24374
|
+
]);
|
|
24375
|
+
var INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
24376
|
+
var CHECK_INTERVAL_MS = 60 * 1000;
|
|
24377
|
+
var activeSessions = new Map;
|
|
24378
|
+
var inactivityTimer = null;
|
|
24379
|
+
var endCallback = null;
|
|
24380
|
+
var clientGetter = null;
|
|
24381
|
+
function initAutoSession(callback, getClient2) {
|
|
24382
|
+
endCallback = callback;
|
|
24383
|
+
clientGetter = getClient2;
|
|
24384
|
+
if (inactivityTimer)
|
|
24385
|
+
clearInterval(inactivityTimer);
|
|
24386
|
+
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
24387
|
+
}
|
|
24388
|
+
async function trackActivity(cardId, options) {
|
|
24389
|
+
const now = Date.now();
|
|
24390
|
+
const existing = activeSessions.get(cardId);
|
|
24391
|
+
if (existing) {
|
|
24392
|
+
existing.lastActivityAt = now;
|
|
24393
|
+
return;
|
|
24394
|
+
}
|
|
24395
|
+
if (!options?.autoStart)
|
|
24396
|
+
return;
|
|
24397
|
+
const client3 = options?.client ?? clientGetter?.();
|
|
24398
|
+
if (!client3)
|
|
24399
|
+
return;
|
|
24400
|
+
const toEnd = [];
|
|
24401
|
+
for (const [otherCardId, session] of activeSessions) {
|
|
24402
|
+
if (otherCardId !== cardId && !session.isExplicit) {
|
|
24403
|
+
toEnd.push(otherCardId);
|
|
24404
|
+
}
|
|
24405
|
+
}
|
|
24406
|
+
for (const otherCardId of toEnd) {
|
|
24407
|
+
await autoEndSession(client3, otherCardId, "completed");
|
|
24408
|
+
}
|
|
24409
|
+
try {
|
|
24410
|
+
await client3.startAgentSession(cardId, {
|
|
24411
|
+
agentIdentifier: "auto",
|
|
24412
|
+
agentName: "Auto-detected Agent",
|
|
24413
|
+
status: "working"
|
|
24414
|
+
});
|
|
24415
|
+
} catch {}
|
|
24416
|
+
activeSessions.set(cardId, {
|
|
24417
|
+
cardId,
|
|
24418
|
+
startedAt: now,
|
|
24419
|
+
lastActivityAt: now,
|
|
24420
|
+
isExplicit: false,
|
|
24421
|
+
agentIdentifier: "auto",
|
|
24422
|
+
agentName: "Auto-detected Agent"
|
|
24423
|
+
});
|
|
24424
|
+
}
|
|
24425
|
+
function markExplicit(cardId) {
|
|
24426
|
+
const existing = activeSessions.get(cardId);
|
|
24427
|
+
if (existing) {
|
|
24428
|
+
existing.isExplicit = true;
|
|
24429
|
+
} else {
|
|
24430
|
+
activeSessions.set(cardId, {
|
|
24431
|
+
cardId,
|
|
24432
|
+
startedAt: Date.now(),
|
|
24433
|
+
lastActivityAt: Date.now(),
|
|
24434
|
+
isExplicit: true,
|
|
24435
|
+
agentIdentifier: "explicit",
|
|
24436
|
+
agentName: "Explicit Agent"
|
|
24437
|
+
});
|
|
24438
|
+
}
|
|
24439
|
+
}
|
|
24440
|
+
function untrack(cardId) {
|
|
24441
|
+
activeSessions.delete(cardId);
|
|
24442
|
+
}
|
|
24443
|
+
async function shutdownAllSessions() {
|
|
24444
|
+
const client3 = clientGetter?.();
|
|
24445
|
+
if (!client3)
|
|
24446
|
+
return;
|
|
24447
|
+
const cardIds = [...activeSessions.keys()];
|
|
24448
|
+
const promises = cardIds.map((cardId) => autoEndSession(client3, cardId, "paused"));
|
|
24449
|
+
await Promise.allSettled(promises);
|
|
24450
|
+
}
|
|
24451
|
+
function destroyAutoSession() {
|
|
24452
|
+
if (inactivityTimer) {
|
|
24453
|
+
clearInterval(inactivityTimer);
|
|
24454
|
+
inactivityTimer = null;
|
|
24455
|
+
}
|
|
24456
|
+
activeSessions.clear();
|
|
24457
|
+
endCallback = null;
|
|
24458
|
+
clientGetter = null;
|
|
24459
|
+
}
|
|
24460
|
+
function checkInactivity() {
|
|
24461
|
+
const now = Date.now();
|
|
24462
|
+
const client3 = clientGetter?.();
|
|
24463
|
+
if (!client3)
|
|
24464
|
+
return;
|
|
24465
|
+
const entries = [...activeSessions.entries()];
|
|
24466
|
+
for (const [cardId, session] of entries) {
|
|
24467
|
+
if (session.isExplicit)
|
|
24468
|
+
continue;
|
|
24469
|
+
if (now - session.lastActivityAt > INACTIVITY_TIMEOUT_MS) {
|
|
24470
|
+
autoEndSession(client3, cardId, "completed").catch(() => {});
|
|
24471
|
+
}
|
|
24472
|
+
}
|
|
24473
|
+
}
|
|
24474
|
+
async function autoEndSession(client3, cardId, status) {
|
|
24475
|
+
activeSessions.delete(cardId);
|
|
24476
|
+
try {
|
|
24477
|
+
await client3.endAgentSession(cardId, { status });
|
|
24478
|
+
} catch {}
|
|
24479
|
+
try {
|
|
24480
|
+
await endCallback?.(client3, cardId, status);
|
|
24481
|
+
} catch {}
|
|
24482
|
+
}
|
|
24483
|
+
|
|
24268
24484
|
// src/consolidation.ts
|
|
24269
24485
|
async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
24270
24486
|
const dryRun = options?.dryRun !== false;
|
|
@@ -25926,6 +26142,22 @@ var TOOLS = {
|
|
|
25926
26142
|
required: ["cardId"]
|
|
25927
26143
|
}
|
|
25928
26144
|
},
|
|
26145
|
+
harmony_get_agent_profile: {
|
|
26146
|
+
description: "Get aggregate performance profile for an agent. Shows total sessions, completion rate, average duration, and more. Defaults to the current agent if no identifier provided.",
|
|
26147
|
+
inputSchema: {
|
|
26148
|
+
type: "object",
|
|
26149
|
+
properties: {
|
|
26150
|
+
agentIdentifier: {
|
|
26151
|
+
type: "string",
|
|
26152
|
+
description: "Agent identifier (e.g., 'claude-code'). Defaults to current agent."
|
|
26153
|
+
},
|
|
26154
|
+
workspaceId: {
|
|
26155
|
+
type: "string",
|
|
26156
|
+
description: "Workspace ID (optional if context set)"
|
|
26157
|
+
}
|
|
26158
|
+
}
|
|
26159
|
+
}
|
|
26160
|
+
},
|
|
25929
26161
|
harmony_generate_prompt: {
|
|
25930
26162
|
description: "Generate an AI-ready prompt from a card. Automatically infers role and focus based on labels (bug, feature, design, etc.). Use this to create context-rich prompts for working on cards.",
|
|
25931
26163
|
inputSchema: {
|
|
@@ -26783,6 +27015,62 @@ async function runEndSessionPipeline(client3, deps, cardId, sessionStatus, endPr
|
|
|
26783
27015
|
const learningResult = await extractLearnings(client3, sessionContext);
|
|
26784
27016
|
learningsExtracted = learningResult.count;
|
|
26785
27017
|
} catch {}
|
|
27018
|
+
try {
|
|
27019
|
+
const workspaceId = deps.getActiveWorkspaceId();
|
|
27020
|
+
const agentId = sessionData?.agent_identifier || "unknown";
|
|
27021
|
+
if (workspaceId && agentId !== "unknown") {
|
|
27022
|
+
(async () => {
|
|
27023
|
+
try {
|
|
27024
|
+
await client3.refreshAgentProfiles(workspaceId);
|
|
27025
|
+
const { profile } = await client3.getAgentProfile(workspaceId, agentId);
|
|
27026
|
+
if (profile) {
|
|
27027
|
+
const p = profile;
|
|
27028
|
+
const title = `Agent Profile: ${agentId}`;
|
|
27029
|
+
const content = [
|
|
27030
|
+
`## ${agentId} Performance Profile`,
|
|
27031
|
+
"",
|
|
27032
|
+
`- **Total sessions:** ${p.total_sessions}`,
|
|
27033
|
+
`- **Completed:** ${p.completed_sessions} (${p.completion_rate_pct}%)`,
|
|
27034
|
+
`- **Paused:** ${p.paused_sessions}`,
|
|
27035
|
+
`- **Blocked:** ${p.blocked_sessions}`,
|
|
27036
|
+
`- **Avg duration:** ${Math.round(Number(p.avg_active_duration_ms || 0) / 1000)}s`,
|
|
27037
|
+
`- **Avg progress:** ${p.avg_completion_progress}%`,
|
|
27038
|
+
`- **First session:** ${p.first_session_at}`,
|
|
27039
|
+
`- **Last session:** ${p.last_session_at}`
|
|
27040
|
+
].join(`
|
|
27041
|
+
`);
|
|
27042
|
+
const existing = await client3.listMemoryEntities({
|
|
27043
|
+
workspace_id: workspaceId,
|
|
27044
|
+
type: "agent",
|
|
27045
|
+
limit: 50
|
|
27046
|
+
});
|
|
27047
|
+
const entities = existing.entities || [];
|
|
27048
|
+
const match = entities.find((e) => e.title === title || e.agent_identifier === agentId);
|
|
27049
|
+
if (match) {
|
|
27050
|
+
await client3.updateMemoryEntity(match.id, {
|
|
27051
|
+
content,
|
|
27052
|
+
confidence: 1,
|
|
27053
|
+
metadata: p
|
|
27054
|
+
});
|
|
27055
|
+
} else {
|
|
27056
|
+
await client3.createMemoryEntity({
|
|
27057
|
+
workspace_id: workspaceId,
|
|
27058
|
+
type: "agent",
|
|
27059
|
+
scope: "workspace",
|
|
27060
|
+
memory_tier: "reference",
|
|
27061
|
+
title,
|
|
27062
|
+
content,
|
|
27063
|
+
confidence: 1,
|
|
27064
|
+
tags: ["agent-profile", agentId],
|
|
27065
|
+
agent_identifier: agentId,
|
|
27066
|
+
metadata: p
|
|
27067
|
+
});
|
|
27068
|
+
}
|
|
27069
|
+
}
|
|
27070
|
+
} catch {}
|
|
27071
|
+
})();
|
|
27072
|
+
}
|
|
27073
|
+
} catch {}
|
|
26786
27074
|
try {
|
|
26787
27075
|
const feedbackResult = await recordContextFeedback(client3, cardId, sessionStatus, endProgressPercent, (sessionData?.blockers?.length ?? 0) > 0);
|
|
26788
27076
|
feedbackAdjusted = feedbackResult.adjusted;
|
|
@@ -27277,18 +27565,18 @@ async function handleToolCall(name, args, deps) {
|
|
|
27277
27565
|
});
|
|
27278
27566
|
untrack(cardId);
|
|
27279
27567
|
let movedTo = null;
|
|
27280
|
-
|
|
27281
|
-
let
|
|
27282
|
-
let
|
|
27283
|
-
let
|
|
27284
|
-
let
|
|
27568
|
+
const _learningsExtracted = 0;
|
|
27569
|
+
let _cardTitle = "";
|
|
27570
|
+
let _cardLabels = [];
|
|
27571
|
+
let _cardDescription = "";
|
|
27572
|
+
let _cardSubtasks = [];
|
|
27285
27573
|
try {
|
|
27286
27574
|
const { card } = await client3.getCard(cardId);
|
|
27287
27575
|
const typedCard = card;
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27291
|
-
|
|
27576
|
+
_cardTitle = typedCard.title || "";
|
|
27577
|
+
_cardLabels = (typedCard.labels || []).map((l) => l.name);
|
|
27578
|
+
_cardDescription = typedCard.description || "";
|
|
27579
|
+
_cardSubtasks = (typedCard.subtasks || []).map((s) => ({
|
|
27292
27580
|
title: s.title,
|
|
27293
27581
|
done: s.done
|
|
27294
27582
|
}));
|
|
@@ -27331,6 +27619,18 @@ async function handleToolCall(name, args, deps) {
|
|
|
27331
27619
|
});
|
|
27332
27620
|
return { success: true, ...result };
|
|
27333
27621
|
}
|
|
27622
|
+
case "harmony_get_agent_profile": {
|
|
27623
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
27624
|
+
if (!workspaceId) {
|
|
27625
|
+
return {
|
|
27626
|
+
success: false,
|
|
27627
|
+
error: "No workspace context. Provide workspaceId or set context."
|
|
27628
|
+
};
|
|
27629
|
+
}
|
|
27630
|
+
const agentIdentifier = args.agentIdentifier || "claude-code";
|
|
27631
|
+
const result = await client3.getAgentProfile(workspaceId, agentIdentifier);
|
|
27632
|
+
return { success: true, ...result };
|
|
27633
|
+
}
|
|
27334
27634
|
case "harmony_generate_prompt": {
|
|
27335
27635
|
let cardData;
|
|
27336
27636
|
let columnData = null;
|