@markus-global/cli 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/markus.mjs CHANGED
@@ -5184,12 +5184,45 @@ ${truncatedContent}
5184
5184
  `;
5185
5185
  }
5186
5186
  if (updated.length > MEMORY_MD_TOTAL_MAX_CHARS) {
5187
- log5.warn("MEMORY.md total size exceeds limit, refusing write", {
5187
+ log5.warn("MEMORY.md total size exceeds limit, attempting compression", {
5188
5188
  key: key2,
5189
5189
  fileSize: updated.length,
5190
5190
  limit: MEMORY_MD_TOTAL_MAX_CHARS
5191
5191
  });
5192
- return;
5192
+ const compressed = this.compressLongTermMemory();
5193
+ if (compressed.charsAfter < updated.length) {
5194
+ log5.info("Compression freed space, retrying write", {
5195
+ key: key2,
5196
+ charsFreed: updated.length - compressed.charsAfter
5197
+ });
5198
+ existing = readFileSync4(this.longTermFile, "utf-8");
5199
+ if (existing.includes(sectionHeader)) {
5200
+ const regex = new RegExp(`(## ${key2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})\\n[\\s\\S]*?(?=\\n## |$)`);
5201
+ updated = existing.replace(regex, `${sectionHeader}
5202
+ ${truncatedContent}
5203
+ `);
5204
+ } else {
5205
+ updated = existing + `
5206
+ ${sectionHeader}
5207
+ ${truncatedContent}
5208
+ `;
5209
+ }
5210
+ if (updated.length > MEMORY_MD_TOTAL_MAX_CHARS) {
5211
+ log5.warn("MEMORY.md still exceeds limit even after compression, refusing write", {
5212
+ key: key2,
5213
+ fileSize: updated.length,
5214
+ limit: MEMORY_MD_TOTAL_MAX_CHARS
5215
+ });
5216
+ return;
5217
+ }
5218
+ } else {
5219
+ log5.warn("MEMORY.md still exceeds limit after compression, refusing write", {
5220
+ key: key2,
5221
+ fileSize: updated.length,
5222
+ limit: MEMORY_MD_TOTAL_MAX_CHARS
5223
+ });
5224
+ return;
5225
+ }
5193
5226
  }
5194
5227
  writeFileSync3(this.longTermFile, updated);
5195
5228
  log5.debug("Long-term memory updated", { key: key2, sectionChars: truncatedContent.length, totalChars: updated.length });
@@ -5365,6 +5398,62 @@ ${tail}`;
5365
5398
  this.saveDebounce = null;
5366
5399
  }, 1e3);
5367
5400
  }
5401
+ /** Compress MEMORY.md — truncate oversized sections to prevent context bloat */
5402
+ compressLongTermMemory() {
5403
+ if (!existsSync6(this.longTermFile)) {
5404
+ return { charsBefore: 0, charsAfter: 0, sectionsBefore: 0, sectionsAfter: 0, truncatedChunks: 0 };
5405
+ }
5406
+ const content = readFileSync4(this.longTermFile, "utf-8");
5407
+ const charsBefore = content.length;
5408
+ const lines = content.split("\n");
5409
+ let i = 0;
5410
+ const preambleLines = [];
5411
+ while (i < lines.length && !lines[i].startsWith("## ")) {
5412
+ preambleLines.push(lines[i]);
5413
+ i++;
5414
+ }
5415
+ const sections = [];
5416
+ let currentHeader = "";
5417
+ let currentBody = [];
5418
+ while (i < lines.length) {
5419
+ const line = lines[i];
5420
+ if (line.startsWith("## ")) {
5421
+ if (currentHeader) {
5422
+ sections.push({ headerLine: currentHeader, body: currentBody });
5423
+ }
5424
+ currentHeader = line;
5425
+ currentBody = [];
5426
+ } else {
5427
+ currentBody.push(line);
5428
+ }
5429
+ i++;
5430
+ }
5431
+ if (currentHeader) {
5432
+ sections.push({ headerLine: currentHeader, body: currentBody });
5433
+ }
5434
+ const sectionsBefore = sections.length;
5435
+ let truncatedChunks = 0;
5436
+ const outputLines = [...preambleLines];
5437
+ for (const section4 of sections) {
5438
+ const bodyStr = section4.body.join("\n");
5439
+ if (bodyStr.length > MEMORY_MD_SECTION_MAX_CHARS) {
5440
+ const truncatedBody = bodyStr.slice(0, MEMORY_MD_SECTION_MAX_CHARS);
5441
+ outputLines.push(section4.headerLine, truncatedBody);
5442
+ truncatedChunks++;
5443
+ } else {
5444
+ outputLines.push(section4.headerLine, bodyStr);
5445
+ }
5446
+ }
5447
+ const compressed = outputLines.join("\n");
5448
+ writeFileSync3(this.longTermFile, compressed);
5449
+ return {
5450
+ charsBefore,
5451
+ charsAfter: compressed.length,
5452
+ sectionsBefore,
5453
+ sectionsAfter: sections.length,
5454
+ truncatedChunks
5455
+ };
5456
+ }
5368
5457
  };
5369
5458
  }
5370
5459
  });
@@ -43522,6 +43611,15 @@ var init_attention = __esm({
43522
43611
  clearPreemptionSignal() {
43523
43612
  this.criticalInterruptResolve = void 0;
43524
43613
  }
43614
+ /**
43615
+ * Clear the lastYieldDecision flag set by checkYieldPoint().
43616
+ * Used by processing code that handles preemption internally (e.g. task
43617
+ * execution delegates resumption to TaskService) so the attention loop
43618
+ * completes the item normally instead of deferring it.
43619
+ */
43620
+ clearLastYieldDecision() {
43621
+ this.lastYieldDecision = void 0;
43622
+ }
43525
43623
  /**
43526
43624
  * Called from the Agent's tool loop at safe yield points (between LLM turns).
43527
43625
  * If an interrupt signal is pending, evaluates whether to continue or switch.
@@ -45629,11 +45727,19 @@ ${notification.stdoutTail}`);
45629
45727
  }
45630
45728
  case "task_status_update": {
45631
45729
  if (extra.triggerExecution && item.payload.taskId) {
45730
+ if (typeof extra.onLog !== "function") {
45731
+ log17.info("Skipping resurfaced task execution item (closures lost)", {
45732
+ agentId: this.id,
45733
+ taskId: item.payload.taskId
45734
+ });
45735
+ resolveResponse("");
45736
+ return;
45737
+ }
45632
45738
  const taskId2 = item.payload.taskId;
45633
45739
  const description = item.payload.content;
45634
- const onLog = extra.onLog ?? (() => {
45635
- });
45740
+ const onLog = extra.onLog;
45636
45741
  await this.executeTask(taskId2, description, onLog, extra.cancelToken, extra.taskProjectContext, extra.executionRound, item.payload.requirementId);
45742
+ this.attentionController.clearLastYieldDecision();
45637
45743
  resolveResponse("");
45638
45744
  return;
45639
45745
  }
@@ -47127,6 +47233,12 @@ ${chatYield.item.payload.content}`;
47127
47233
  }
47128
47234
  }
47129
47235
  async handleMessageStream(userMessage, onEvent, senderId, senderInfo, cancelToken, images, fileNames) {
47236
+ if (cancelToken?.cancelled) {
47237
+ log17.info("Stream cancelled before processing started", { agentId: this.id });
47238
+ if (this.activeTasks.size === 0)
47239
+ this.setStatus("idle");
47240
+ return "";
47241
+ }
47130
47242
  if (this.activeTasks.size === 0) {
47131
47243
  this.setStatus("working");
47132
47244
  }
@@ -47248,10 +47360,11 @@ ${chatYield.item.payload.content}`;
47248
47360
  }
47249
47361
  if (cancelToken?.cancelled) {
47250
47362
  log17.info("Stream cancelled by user during tool loop", { agentId: this.id });
47251
- if (lastResponseContent && this.currentSessionId) {
47363
+ if (this.currentSessionId) {
47364
+ const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
47252
47365
  this.memory.appendMessage(this.currentSessionId, {
47253
47366
  role: "assistant",
47254
- content: lastResponseContent + "\n\n[interrupted by user]"
47367
+ content
47255
47368
  });
47256
47369
  }
47257
47370
  if (streamChatActivityId)
@@ -47271,6 +47384,19 @@ ${chatYield.item.payload.content}`;
47271
47384
  content: "[Continue from where you left off. Do not repeat what you already said.]"
47272
47385
  });
47273
47386
  } else {
47387
+ if (cancelToken?.cancelled) {
47388
+ log17.info("Stream cancelled before tool execution", { agentId: this.id });
47389
+ this.memory.appendMessage(this.currentSessionId, {
47390
+ role: "assistant",
47391
+ content: response.content + "\n\n[interrupted by user]",
47392
+ reasoningContent: response.reasoningContent
47393
+ });
47394
+ if (streamChatActivityId)
47395
+ this.endActivity(streamChatActivityId);
47396
+ if (this.activeTasks.size === 0)
47397
+ this.setStatus("idle");
47398
+ return response.content || lastResponseContent || "";
47399
+ }
47274
47400
  this.memory.appendMessage(this.currentSessionId, {
47275
47401
  role: "assistant",
47276
47402
  content: response.content,
@@ -47373,10 +47499,11 @@ ${streamYield.item.payload.content}`;
47373
47499
  const updatedMessages = preparedCont.messages;
47374
47500
  if (cancelToken?.cancelled) {
47375
47501
  log17.info("Stream cancelled before LLM re-call", { agentId: this.id });
47376
- if (lastResponseContent && this.currentSessionId) {
47502
+ if (this.currentSessionId) {
47503
+ const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
47377
47504
  this.memory.appendMessage(this.currentSessionId, {
47378
47505
  role: "assistant",
47379
- content: lastResponseContent + "\n\n[interrupted by user]"
47506
+ content
47380
47507
  });
47381
47508
  }
47382
47509
  if (streamChatActivityId)
@@ -47433,11 +47560,12 @@ ${streamYield.item.payload.content}`;
47433
47560
  if (streamChatActivityId)
47434
47561
  this.endActivity(streamChatActivityId, { success: !cancelToken?.cancelled });
47435
47562
  if (cancelToken?.cancelled) {
47436
- if (lastResponseContent && this.currentSessionId) {
47563
+ if (this.currentSessionId) {
47437
47564
  try {
47565
+ const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
47438
47566
  this.memory.appendMessage(this.currentSessionId, {
47439
47567
  role: "assistant",
47440
- content: lastResponseContent + "\n\n[interrupted by user]"
47568
+ content
47441
47569
  });
47442
47570
  } catch {
47443
47571
  }
@@ -49240,7 +49368,8 @@ ${promo.content}` : promo.content;
49240
49368
  }
49241
49369
  /**
49242
49370
  * Enforce MEMORY.md hygiene: remove daily-report sections (they belong in
49243
- * daily-logs/), strip leaked LLM <think> blocks, and enforce section size limits.
49371
+ * daily-logs/), strip leaked LLM <think> blocks, and enforce section/total
49372
+ * size limits via heuristic compression.
49244
49373
  */
49245
49374
  pruneMemoryMd() {
49246
49375
  const content = this.memory.getLongTermMemory();
@@ -49280,6 +49409,15 @@ ${promo.content}` : promo.content;
49280
49409
  writeFileSync8(memoryMdPath, pruned + "\n");
49281
49410
  log17.info("Pruned MEMORY.md: removed daily-report sections and LLM artifacts", { agentId: this.id });
49282
49411
  }
49412
+ const compressed = this.memory.compressLongTermMemory();
49413
+ if (compressed.truncatedChunks > 0) {
49414
+ log17.info("Compressed MEMORY.md during Dream Cycle", {
49415
+ agentId: this.id,
49416
+ charsBefore: compressed.charsBefore,
49417
+ charsAfter: compressed.charsAfter,
49418
+ sectionsTrimmed: compressed.truncatedChunks
49419
+ });
49420
+ }
49283
49421
  }
49284
49422
  };
49285
49423
  }
@@ -52395,16 +52533,23 @@ function createMemoryTools(ctx) {
52395
52533
  let entries2 = semResults.map((r) => r.entry);
52396
52534
  if (type)
52397
52535
  entries2 = entries2.filter((e) => e.type === type);
52398
- log24.debug("Semantic memory search", { agentId: ctx.agentId, query: query2, results: entries2.length });
52399
- return JSON.stringify({
52400
- results: entries2.map((e) => ({
52401
- id: e.id,
52402
- type: e.type,
52403
- content: e.content,
52404
- timestamp: e.timestamp,
52405
- similarity: semResults.find((r) => r.entry.id === e.id)?.similarity
52406
- })),
52407
- count: entries2.length
52536
+ if (entries2.length > 0) {
52537
+ log24.debug("Semantic memory search", { agentId: ctx.agentId, query: query2, results: entries2.length });
52538
+ return JSON.stringify({
52539
+ results: entries2.map((e) => ({
52540
+ id: e.id,
52541
+ type: e.type,
52542
+ content: e.content,
52543
+ timestamp: e.timestamp,
52544
+ similarity: semResults.find((r) => r.entry.id === e.id)?.similarity
52545
+ })),
52546
+ count: entries2.length,
52547
+ searchMethod: "semantic"
52548
+ });
52549
+ }
52550
+ log24.info("Semantic search returned 0 results, falling back to substring", {
52551
+ agentId: ctx.agentId,
52552
+ query: query2
52408
52553
  });
52409
52554
  } catch (err) {
52410
52555
  log24.warn("Semantic search failed, falling back to substring", { error: String(err) });
@@ -52423,7 +52568,8 @@ function createMemoryTools(ctx) {
52423
52568
  timestamp: e.timestamp,
52424
52569
  tags: e.metadata?.tags
52425
52570
  })),
52426
- count: results.length
52571
+ count: results.length,
52572
+ searchMethod: "substring"
52427
52573
  });
52428
52574
  }
52429
52575
  },
@@ -53143,29 +53289,34 @@ var init_semantic_search = __esm({
53143
53289
  log27.warn("Failed to index memory entry", { id: entry.id, error: String(err) });
53144
53290
  }
53145
53291
  }
53292
+ /**
53293
+ * Search indexed memories by semantic similarity.
53294
+ *
53295
+ * Returns an empty array when the service is not enabled.
53296
+ *
53297
+ * @throws {Error} Propagates embedding or vector-store errors to the caller
53298
+ * so that callers (e.g. memory_search tool) can implement their own
53299
+ * fallback strategy. Contrast with {@link indexMemory}, which silently
53300
+ * swallows errors because indexing is best-effort.
53301
+ */
53146
53302
  async search(query2, opts) {
53147
53303
  if (!this.enabled)
53148
53304
  return [];
53149
- try {
53150
- const queryEmbedding = await this.embedding.embed(query2);
53151
- const results = await this.vectorStore.search(queryEmbedding, {
53152
- topK: opts?.topK ?? 5,
53153
- agentId: opts?.agentId,
53154
- minSimilarity: opts?.minSimilarity ?? 0.3
53155
- });
53156
- return results.map((r) => ({
53157
- entry: {
53158
- id: r.id,
53159
- content: r.content,
53160
- type: r.type,
53161
- timestamp: ""
53162
- },
53163
- similarity: r.similarity
53164
- }));
53165
- } catch (err) {
53166
- log27.warn("Semantic search failed, returning empty results", { error: String(err) });
53167
- return [];
53168
- }
53305
+ const queryEmbedding = await this.embedding.embed(query2);
53306
+ const results = await this.vectorStore.search(queryEmbedding, {
53307
+ topK: opts?.topK ?? 5,
53308
+ agentId: opts?.agentId,
53309
+ minSimilarity: opts?.minSimilarity ?? 0.3
53310
+ });
53311
+ return results.map((r) => ({
53312
+ entry: {
53313
+ id: r.id,
53314
+ content: r.content,
53315
+ type: r.type,
53316
+ timestamp: ""
53317
+ },
53318
+ similarity: r.similarity
53319
+ }));
53169
53320
  }
53170
53321
  async deleteMemory(id) {
53171
53322
  if (!this.enabled)
@@ -53711,6 +53862,25 @@ You are ${request.name}.`,
53711
53862
  "- [ ] Scan recent channel messages for anything requiring attention"
53712
53863
  ].join("\n"), "utf-8");
53713
53864
  }
53865
+ const sessionsDir = join12(agentDataDir, "sessions");
53866
+ const dailyLogsDir = join12(agentDataDir, "daily-logs");
53867
+ const memoryPath = join12(agentDataDir, "MEMORY.md");
53868
+ mkdirSync11(sessionsDir, { recursive: true });
53869
+ mkdirSync11(dailyLogsDir, { recursive: true });
53870
+ if (!existsSync17(memoryPath)) {
53871
+ writeFileSync10(memoryPath, [
53872
+ "# Agent Memory",
53873
+ "",
53874
+ "## Your Knowledge",
53875
+ "",
53876
+ "## procedures",
53877
+ "",
53878
+ "## architecture",
53879
+ "",
53880
+ "## lessons-learned",
53881
+ ""
53882
+ ].join("\n"), "utf-8");
53883
+ }
53714
53884
  const config = {
53715
53885
  id,
53716
53886
  name: request.name,
@@ -64414,7 +64584,7 @@ var init_builder_service = __esm({
64414
64584
  const memberRole = member.role ?? "worker";
64415
64585
  const memberName = member.name ?? "Agent";
64416
64586
  const memberSkills = member.skills ?? [];
64417
- const memberFilesDir = this.findMemberDir(artDir, memberName, usedMemberDirs);
64587
+ const memberFilesDir = this.findMemberDir(artDir, memberName, usedMemberDirs, member.roleName);
64418
64588
  const hasCustomRole = !!memberFilesDir && existsSync24(join20(memberFilesDir, "ROLE.md"));
64419
64589
  if (memberFilesDir)
64420
64590
  usedMemberDirs.add(memberFilesDir);
@@ -64459,14 +64629,24 @@ var init_builder_service = __esm({
64459
64629
  * Find the member directory under artDir/members/ by trying multiple slug strategies.
64460
64630
  * Returns the absolute path to the member directory, or null if not found.
64461
64631
  */
64462
- findMemberDir(artDir, memberName, usedDirs) {
64632
+ findMemberDir(artDir, memberName, usedDirs, roleName) {
64463
64633
  const membersBase = join20(artDir, "members");
64464
64634
  if (!existsSync24(membersBase))
64465
64635
  return null;
64466
- const slug = kebab(memberName, "agent");
64467
- const exact = join20(membersBase, slug);
64468
- if (existsSync24(exact) && !usedDirs.has(exact))
64469
- return exact;
64636
+ const nameSlug = kebab(memberName);
64637
+ if (nameSlug && !/^pkg-/.test(nameSlug)) {
64638
+ const exact = join20(membersBase, nameSlug);
64639
+ if (existsSync24(exact) && !usedDirs.has(exact))
64640
+ return exact;
64641
+ }
64642
+ if (roleName) {
64643
+ const roleSlug = kebab(roleName);
64644
+ if (roleSlug && !/^pkg-/.test(roleSlug)) {
64645
+ const roleDir = join20(membersBase, roleSlug);
64646
+ if (existsSync24(roleDir) && !usedDirs.has(roleDir))
64647
+ return roleDir;
64648
+ }
64649
+ }
64470
64650
  try {
64471
64651
  for (const entry of readdirSync8(membersBase, { withFileTypes: true })) {
64472
64652
  if (!entry.isDirectory())
@@ -64480,8 +64660,12 @@ var init_builder_service = __esm({
64480
64660
  try {
64481
64661
  const content = readFileSync18(rolePath, "utf-8");
64482
64662
  const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
64483
- if (title && title.toLowerCase() === memberName.toLowerCase())
64484
- return candidateDir;
64663
+ if (title) {
64664
+ const tLower = title.toLowerCase();
64665
+ const nLower = memberName.toLowerCase();
64666
+ if (tLower === nLower || tLower.includes(nLower) || nLower.includes(tLower))
64667
+ return candidateDir;
64668
+ }
64485
64669
  } catch {
64486
64670
  }
64487
64671
  }
@@ -64997,6 +65181,9 @@ var init_sse_handler = __esm({
64997
65181
  } else if (this.options.isResume) {
64998
65182
  this.sessionId = this.options.sessionId ?? null;
64999
65183
  }
65184
+ if (this.sessionId && this.sseBuffer && !this.sseDisconnected) {
65185
+ this.sseBuffer.send({ type: "session_start", sessionId: this.sessionId });
65186
+ }
65000
65187
  const reply = await this.options.agent.sendMessageStream(this.options.userText, (event) => this.handleStreamEvent(event), this.options.senderId, this.options.senderInfo, this.cancelToken, this.options.images, this.options.fileNames);
65001
65188
  const finalNow = (/* @__PURE__ */ new Date()).toISOString();
65002
65189
  let finalThinking;
@@ -70309,9 +70496,9 @@ EXPLANATION_END`;
70309
70496
  writeFileSync16(join22(artDir, "NORMS.md"), norms, "utf-8");
70310
70497
  }
70311
70498
  const rawMembers = Array.isArray(artifact.team?.members) ? artifact.team.members : Array.isArray(artifact.members) ? artifact.members : [];
70312
- for (const m of rawMembers) {
70499
+ for (const [idx, m] of rawMembers.entries()) {
70313
70500
  const mName = m.name ?? "Agent";
70314
- const slug = kebab(mName, "agent");
70501
+ const slug = kebab(mName, "member-" + idx);
70315
70502
  const memberDir = join22(artDir, "members", slug);
70316
70503
  const roleContent = m.roleContent || m.role_md;
70317
70504
  const policiesContent = m.policiesContent || m.policies_md;
@@ -72660,11 +72847,11 @@ EXPLANATION_END`;
72660
72847
  }
72661
72848
  const rolesRoot = rolesCandidates.find((d) => ex(d)) ?? rolesDir;
72662
72849
  const files = {};
72663
- for (const member of tpl.members) {
72850
+ for (const [idx, member] of tpl.members.entries()) {
72664
72851
  const roleName = member.roleName;
72665
72852
  if (!roleName)
72666
72853
  continue;
72667
- const memberSlug = (member.name ?? roleName).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
72854
+ const memberSlug = kebab(member.name ?? roleName, "member-" + idx);
72668
72855
  const roleDir = resolve13(rolesRoot, roleName);
72669
72856
  if (!ex(roleDir))
72670
72857
  continue;