dsh-mnemon 0.5.38 → 0.5.40

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/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { f as canonicalWorkspacePath, m as withMemoryStorageLock, n as memoryStrategyConfigurationValues, p as workspaceStorageId, r as readMemoryStrategyConfiguration, s as receipt } from "./strategy-configuration-CKP2kRss.js";
2
- import { a as validateMemoryPluginGraph, i as captureMemoryContributionSnapshot, n as isMemoryCompositionUnavailable, o as prepareMemoryContributions, r as MemoryCompositionGeneration, t as provideMemoryRuntime } from "./runtime-0u6dOiF9.js";
1
+ import { d as truncate, f as canonicalWorkspacePath, m as withMemoryStorageLock, n as memoryStrategyConfigurationValues, p as workspaceStorageId, r as readMemoryStrategyConfiguration, s as receipt } from "./strategy-configuration-CKP2kRss.js";
2
+ import { a as validateMemoryPluginGraph, i as captureMemoryContributionSnapshot, n as isMemoryCompositionUnavailable, o as prepareMemoryContributions, r as MemoryCompositionGeneration, t as provideMemoryRuntime } from "./runtime-DmL0It3P.js";
3
3
  import { DEFAULT_MEMORY_VIEW_BUDGET, MEMORY_CAPABILITIES } from "./contracts.js";
4
4
  import { r as defineMemoryPlugin, t as canonicalMemoryJson } from "./definitions-DU7MLBhu.js";
5
5
  import { Context } from "@deepseek-ai/cordis";
@@ -259,7 +259,7 @@ const MNEMON_SYNC_CHANNEL = "/dsh-mnemon-sync";
259
259
  const MNEMON_SYNC_TOKEN_ENV = "MNEMON_SYNC_GIT_TOKEN";
260
260
  /**
261
261
  * What a storage root publishes before anyone configures it: the branch, the
262
- * directory inside it, and the commit identity used when no author is set. They
262
+ * directory inside it, and the fallback when no author or Git identity is set. They
263
263
  * are declared with the wire constants because the settings form shows the same
264
264
  * defaults the Host would apply to an empty field.
265
265
  */
@@ -387,6 +387,7 @@ const DEFAULT_SKILLS_CONFIG = Object.freeze({
387
387
  evolution: false,
388
388
  allowCreate: true,
389
389
  allowUpdate: true,
390
+ targetScope: "user",
390
391
  autoBacktest: true,
391
392
  hints: true,
392
393
  sidebar: false,
@@ -474,6 +475,11 @@ const SkillsConfig = z.object({
474
475
  evolution: z.boolean().default(DEFAULT_SKILLS_CONFIG.evolution),
475
476
  allowCreate: z.boolean().default(DEFAULT_SKILLS_CONFIG.allowCreate),
476
477
  allowUpdate: z.boolean().default(DEFAULT_SKILLS_CONFIG.allowUpdate),
478
+ targetScope: z.union([
479
+ "storage",
480
+ "user",
481
+ "project"
482
+ ]).default(DEFAULT_SKILLS_CONFIG.targetScope),
477
483
  autoBacktest: z.boolean().default(DEFAULT_SKILLS_CONFIG.autoBacktest),
478
484
  hints: z.boolean().default(DEFAULT_SKILLS_CONFIG.hints),
479
485
  sidebar: z.boolean().default(DEFAULT_SKILLS_CONFIG.sidebar),
@@ -5331,7 +5337,7 @@ var MnemonGitSync = class {
5331
5337
  if (committed) {
5332
5338
  const identity = await this.commitIdentity(settings, signal);
5333
5339
  const result = await this.git([
5334
- ...identity,
5340
+ ...identity.args,
5335
5341
  "commit",
5336
5342
  "--quiet",
5337
5343
  "-m",
@@ -5340,7 +5346,8 @@ var MnemonGitSync = class {
5340
5346
  settings.subdir
5341
5347
  ], {
5342
5348
  cwd: this.store.mirror(),
5343
- signal
5349
+ signal,
5350
+ env: identity.env
5344
5351
  });
5345
5352
  if (result.exitCode !== 0) throw new Error("git commit failed: " + tail(result.stderr || result.stdout));
5346
5353
  }
@@ -5373,20 +5380,35 @@ var MnemonGitSync = class {
5373
5380
  };
5374
5381
  }
5375
5382
  /**
5376
- * The identity a sync commit is recorded under: the one configured here, else the
5377
- * Git identity this machine has. A machine without one gets the channel's own
5378
- * rather than a failed commit, and rather than the account and host names Git
5379
- * would otherwise guess and publish.
5383
+ * The saved default pair also appears in older settings that never selected an
5384
+ * author. Like blank fields, it defers to Git in the mirror's directory; only a
5385
+ * machine without a configured identity uses the channel's placeholder. Any
5386
+ * other pair keeps its explicit fields, even when one matches a default value.
5380
5387
  */
5381
5388
  async commitIdentity(settings, signal) {
5382
- const name = settings.authorName !== "" ? settings.authorName : await this.configured("user.name", signal) ?? "dsh-mnemon sync";
5383
- const email = settings.authorEmail !== "" ? settings.authorEmail : await this.configured("user.email", signal) ?? "mnemon@localhost";
5384
- return [
5385
- "-c",
5386
- "user.name=" + name,
5387
- "-c",
5388
- "user.email=" + email
5389
- ];
5389
+ const defaults = settings.authorName === "dsh-mnemon sync" && settings.authorEmail === "mnemon@localhost";
5390
+ const authorName = defaults ? "" : settings.authorName;
5391
+ const authorEmail = defaults ? "" : settings.authorEmail;
5392
+ const name = authorName !== "" ? authorName : await this.configured("user.name", signal) ?? "dsh-mnemon sync";
5393
+ const email = authorEmail !== "" ? authorEmail : await this.configured("user.email", signal) ?? "mnemon@localhost";
5394
+ const env = {};
5395
+ if (authorName !== "") {
5396
+ env.GIT_AUTHOR_NAME = authorName;
5397
+ env.GIT_COMMITTER_NAME = authorName;
5398
+ }
5399
+ if (authorEmail !== "") {
5400
+ env.GIT_AUTHOR_EMAIL = authorEmail;
5401
+ env.GIT_COMMITTER_EMAIL = authorEmail;
5402
+ }
5403
+ return {
5404
+ args: [
5405
+ "-c",
5406
+ "user.name=" + name,
5407
+ "-c",
5408
+ "user.email=" + email
5409
+ ],
5410
+ env
5411
+ };
5390
5412
  }
5391
5413
  /** One Git setting as this machine resolves it, or nothing when no config sets it. */
5392
5414
  async configured(key, signal) {
@@ -6117,7 +6139,7 @@ var MnemonGitSync = class {
6117
6139
  const failure = rejectedPush(output) ? "push-rejected" : REMOTE_FAILURE[remoteIssueOf(output)];
6118
6140
  return {
6119
6141
  pushed: false,
6120
- reason: credential.token === void 0 && failure !== "push-rejected" ? "the commit stays local because no token is available: " + reason : reason,
6142
+ reason: /\[remote rejected\]/iu.test(output) ? "the remote rejected this push; check its policy and the commit author in Sync > Advanced, then sync again: " + reason : credential.token === void 0 && failure !== "push-rejected" ? "the commit stays local because no token is available: " + reason : reason,
6121
6143
  ...failure === void 0 ? {} : { failure }
6122
6144
  };
6123
6145
  }
@@ -6279,7 +6301,10 @@ var MnemonGitSync = class {
6279
6301
  try {
6280
6302
  return await this.run(GIT_LABEL, prepared.args, {
6281
6303
  timeoutMs: options.timeoutMs ?? 12e4,
6282
- env: gitEnvironment(),
6304
+ env: gitEnvironment({
6305
+ ...process.env,
6306
+ ...options.env
6307
+ }),
6283
6308
  ...options.cwd === void 0 ? {} : { cwd: options.cwd },
6284
6309
  ...options.signal === void 0 ? {} : { signal: options.signal },
6285
6310
  ...options.maxOutputBytes === void 0 ? {} : { maxOutputBytes: options.maxOutputBytes },
@@ -7075,7 +7100,7 @@ var ComposableMemoryTurnManager = class {
7075
7100
  const stored = this.requireTurn(turnId);
7076
7101
  const operation = this.generations.acquire(stored.lease.id);
7077
7102
  try {
7078
- return await operation.generation.executeAction(stored.context.view, offerId, input, authorize, signal);
7103
+ return await operation.generation.executeAction(stored.context.view, offerId, input, authorize, signal, stored.context);
7079
7104
  } finally {
7080
7105
  operation.release();
7081
7106
  }
@@ -10169,201 +10194,486 @@ async function assertAutomaticMemoryWrite(host, parent) {
10169
10194
  return host.writableSourceFor(parent, "memory-spaces", "remember");
10170
10195
  }
10171
10196
  //#endregion
10172
- //#region src/host/review-admission.ts
10173
- const RESULT_TOOL = "mnemon_subagent_result";
10174
- const CHECK_TIMEOUT_MS = 5e3;
10175
- /** A no-op challenge through the same mandatory completion channel as review.
10176
- * Inventory alone cannot see execution-time guards. No errors are classified as
10177
- * permission failures, and an unavailable checkpoint is never a permanent ban.
10197
+ //#region src/host/view-presentation.ts
10198
+ /**
10199
+ * The named tools bound to each Source type's Routes or Actions, from the tool table: these are
10200
+ * existing DSH product-tool bindings, not Core operation names. Tools that share one operation are
10201
+ * listed together, in table order, as the View's tool list names them.
10178
10202
  */
10179
- var AutomaticReviewAdmission = class {
10180
- challenges = /* @__PURE__ */ new Map();
10181
- controllers = /* @__PURE__ */ new Set();
10182
- draining = /* @__PURE__ */ new WeakSet();
10183
- disposed = false;
10184
- /** Only an unexpired, host-issued challenge for this exact Agent can no-op. */
10185
- acknowledge(args, execution) {
10186
- if (typeof args !== "object" || args === null || !("requestId" in args) || typeof args.requestId !== "string") return false;
10187
- const challenge = this.challenges.get(args.requestId);
10188
- if (challenge === void 0 || challenge.agent !== execution.agent || challenge.execution !== void 0) return false;
10189
- execution.signal.throwIfAborted();
10190
- challenge.execution = execution;
10191
- return true;
10192
- }
10193
- dispose() {
10194
- this.disposed = true;
10195
- this.challenges.clear();
10196
- for (const controller of this.controllers) controller.abort();
10197
- }
10198
- async check(agent, signal) {
10199
- signal.throwIfAborted();
10200
- const tools = agent.ctx.tools;
10201
- if (this.disposed || tools?.execute === void 0 || this.draining.has(agent)) return false;
10202
- const controller = new AbortController();
10203
- this.controllers.add(controller);
10204
- const combined = AbortSignal.any([signal, controller.signal]);
10205
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
10206
- timeout.unref?.();
10207
- let rejectAbort;
10208
- const aborted = new Promise((_resolve, reject) => {
10209
- rejectAbort = () => reject(combined.reason);
10210
- combined.addEventListener("abort", rejectAbort, { once: true });
10211
- });
10212
- const attempt = async (codeMode) => {
10213
- combined.throwIfAborted();
10214
- const requestId = randomUUID();
10215
- const callId = randomUUID();
10216
- const challenge = {
10217
- agent,
10218
- accepted: false
10219
- };
10220
- this.challenges.set(requestId, challenge);
10221
- const stops = [];
10222
- let executing = false;
10223
- const cleanup = () => {
10224
- for (const stop of stops.reverse()) stop();
10225
- this.draining.delete(agent);
10226
- };
10227
- try {
10228
- stops.push(agent.ctx.on("tools/result", ((execution, result) => {
10229
- if (execution === challenge.execution) challenge.accepted = result.isError === false;
10230
- })));
10231
- stops.push(agent.ctx.on("tools/pre-execute", (async (execution, next) => {
10232
- const decision = await next();
10233
- return execution.agent === agent && (execution.callId === callId || execution.rootCallId === callId) && decision.kind === "ask" ? { kind: "cancel" } : decision;
10234
- }), { prepend: true }));
10235
- const args = {
10236
- requestId,
10237
- result: {}
10238
- };
10239
- const pending = tools.execute({
10240
- callId,
10241
- agent,
10242
- signal: combined,
10243
- name: codeMode ? "run_code" : RESULT_TOOL,
10244
- arguments: codeMode ? {
10245
- description: "Check automatic memory review availability",
10246
- code: `await tools.mnemon_subagent_result(${JSON.stringify(args)})`
10247
- } : args
10248
- });
10249
- executing = true;
10250
- this.draining.add(agent);
10251
- const settled = pending.finally(cleanup);
10252
- const outcome = await Promise.race([settled, aborted]);
10253
- combined.throwIfAborted();
10254
- return challenge.execution !== void 0 && challenge.accepted && outcome.isError === false;
10255
- } finally {
10256
- this.challenges.delete(requestId);
10257
- if (!executing) cleanup();
10258
- }
10259
- };
10260
- try {
10261
- if (await attempt(false)) return true;
10262
- return tools.get?.("run_code", agent) !== void 0 && await attempt(true);
10263
- } catch {
10264
- signal.throwIfAborted();
10265
- return false;
10266
- } finally {
10267
- clearTimeout(timeout);
10268
- if (rejectAbort !== void 0) combined.removeEventListener("abort", rejectAbort);
10269
- this.controllers.delete(controller);
10270
- }
10203
+ function namedBindings(kind) {
10204
+ const bindings = {};
10205
+ for (const [name, tool] of memoryTools()) {
10206
+ if (tool.sourceTypeId === void 0 || tool.operation === void 0 || tool.kind === "write" !== (kind === "action")) continue;
10207
+ const operations = bindings[tool.sourceTypeId] ??= {};
10208
+ const listed = operations[tool.operation];
10209
+ operations[tool.operation] = listed === void 0 ? name : `${listed} / ${name}`;
10271
10210
  }
10272
- };
10273
- //#endregion
10274
- //#region src/host/subagent.ts
10275
- function evidenceInsights(evidence) {
10276
- return evidence.items.map((item) => {
10277
- return {
10278
- ...optionalObject(item.provenance) ?? {},
10279
- id: item.id,
10280
- content: item.text,
10281
- ...item.score === void 0 ? {} : { score: item.score },
10282
- ...item.revision === void 0 ? {} : { revision: item.revision }
10283
- };
10284
- });
10211
+ return bindings;
10212
+ }
10213
+ const ROUTE_TOOLS = namedBindings("route");
10214
+ const ACTION_TOOLS = namedBindings("action");
10215
+ const NAMED_SOURCE_TYPES = [.../* @__PURE__ */ new Set([...Object.keys(ROUTE_TOOLS), ...Object.keys(ACTION_TOOLS)])];
10216
+ /** Use a short availability list only where the Host has the matching named binding.
10217
+ * Unbound external operations retain exact ids and schemas in the generic envelope. */
10218
+ function modelMemoryWake(graph, turn) {
10219
+ const generation = graph.memoryComposition.generation(turn.view.runtimeGeneration);
10220
+ if (generation === void 0) throw new Error("Cannot present an unpinned Memory generation");
10221
+ return graph.composableTurns.memoryWake(turn.view.id, memoryWakeBindings(generation, turn.view));
10222
+ }
10223
+ function presentMemoryWake(generation, view) {
10224
+ return createMemoryWake(view, memoryWakeBindings(generation, view));
10285
10225
  }
10286
10226
  /**
10287
- * Every task dsh-mnemon delegates to a child Agent starts with one line of its own, such as
10288
- * `[dsh-mnemon:delegated review]` (#393). DSH records the task as the child's prompt from a
10289
- * person, so a plugin that reads sessions, such as one that collects a workspace's work, can
10290
- * rely on this line, documented as stable, to leave Mnemon's own children out.
10227
+ * Why a generic View tool names no id this turn's View offers, told apart (#390); undefined when
10228
+ * the id is offered. It names only what the MNEMON VIEW ROUTES envelope shows: an operation bound
10229
+ * to a named tool is offered through that tool, and the envelope does not list its id.
10291
10230
  */
10292
- const DELEGATED_TASK_PREFIX = "[dsh-mnemon:delegated ";
10293
- function delegatedTaskLine(operation) {
10294
- return `${DELEGATED_TASK_PREFIX}${operation}]`;
10231
+ function unofferedViewId(graph, turn, kind, id) {
10232
+ const view = turn.view;
10233
+ const offered = kind === "route" ? view.routes.map((route) => ({
10234
+ id: route.id,
10235
+ operation: route.sourceRouteId
10236
+ })) : view.actionOffers.map((offer) => ({
10237
+ id: offer.id,
10238
+ operation: offer.sourceActionId
10239
+ }));
10240
+ const label = kind === "route" ? "memory View Route" : "memory ActionOffer";
10241
+ if (typeof id !== "string" || id === "") return `${label} needs ${kind === "route" ? "routeId" : "offerId"}, the exact id of one it offers`;
10242
+ if (offered.some((entry) => entry.id === id)) return void 0;
10243
+ const generation = graph.memoryComposition.generation(view.runtimeGeneration);
10244
+ const bindings = generation === void 0 ? {} : memoryWakeBindings(generation, view);
10245
+ const named = (kind === "route" ? bindings.routes : bindings.actions) ?? {};
10246
+ const short = id.split("/").at(-1);
10247
+ const tools = [...new Set(offered.filter((entry) => named[entry.id] !== void 0 && (entry.operation === short || entry.id.split("/").at(-1) === short)).map((entry) => named[entry.id]))];
10248
+ if (tools.length > 0) return `${label} is unavailable: ${id}; this View offers it as ${tools.join(" and ")}, so call that tool`;
10249
+ const shown = offered.filter((entry) => named[entry.id] === void 0).map((entry) => entry.id);
10250
+ if (shown.length === 0) return `${label} is unavailable: ${id}; MNEMON VIEW ROUTES offers none by id this turn, only the named tools in MNEMON VIEW TOOLS`;
10251
+ const near = shown.filter((candidate) => candidate.split("/").at(-1) === short);
10252
+ if (near.length === 1) return `${label} is unavailable: ${id}; ids are exact as MNEMON VIEW ROUTES lists them: ${JSON.stringify(near[0])}`;
10253
+ const listed = [...near, ...shown.filter((candidate) => !near.includes(candidate))];
10254
+ return `${label} is unavailable: ${id}; MNEMON VIEW ROUTES offers ${listed.slice(0, 12).map((candidate) => JSON.stringify(candidate)).join(", ")}${listed.length > 12 ? ` and ${listed.length - 12} more` : ""}`;
10295
10255
  }
10296
- const WRITE_TOOLS = [
10297
- ...[
10298
- "mnemon_memory_bodies",
10299
- "mnemon_recall",
10300
- "mnemon_related"
10301
- ],
10302
- "mnemon_remember",
10303
- "mnemon_link",
10304
- "mnemon_forget",
10305
- "mnemon_memory_body_create",
10306
- "mnemon_memory_body_update",
10307
- "mnemon_memory_body_merge"
10308
- ];
10309
- const AUTONOMOUS_WRITE_TOOLS = WRITE_TOOLS.filter((tool) => tool !== "mnemon_forget");
10310
- const EXPLICIT_WRITE_TOOLS = WRITE_TOOLS;
10311
- const REVIEW_TOOLS = [
10312
- ...["mnemon_document_search"],
10313
- "mnemon_runtime_memory",
10314
- "mnemon_document_create"
10315
- ];
10316
- const RESULT_TOOL_NAME = "mnemon_subagent_result";
10317
- /** How many finished delegated children stay refused memory tools if DSH resumes one. */
10318
- const FINISHED_CHILDREN = 1e3;
10319
- const FINISHED_CHILD_REFUSAL = "This Mnemon task has finished, so it no longer reads or changes memory. Reply without memory tools.";
10320
- const RESULT_TOOL_INPUT_SCHEMA = {
10321
- type: "object",
10322
- properties: {
10323
- requestId: {
10324
- type: "string",
10325
- minLength: 1
10326
- },
10327
- result: {
10328
- type: "object",
10329
- additionalProperties: true
10256
+ function memoryWakeBindings(generation, view) {
10257
+ const instances = generation.sourceInstances();
10258
+ const routes = {}, actions = {};
10259
+ for (const typeId of NAMED_SOURCE_TYPES) {
10260
+ const selected = namedToolInstance(instances.filter((source) => source.sourceTypeId === typeId), typeId);
10261
+ if (selected === void 0) continue;
10262
+ for (const route of view.routes.filter((route) => route.sourceInstanceKey === selected.sourceInstanceKey)) {
10263
+ if (view.readGrants.find((grant) => grant.id === route.readGrantId)?.schema !== `dsh-mnemon.${typeId}/v1`) continue;
10264
+ const tool = ROUTE_TOOLS[typeId]?.[route.sourceRouteId];
10265
+ if (tool !== void 0) routes[route.id] = tool;
10266
+ }
10267
+ for (const action of view.actionOffers.filter((action) => action.sourceInstanceKey === selected.sourceInstanceKey)) {
10268
+ const tool = ACTION_TOOLS[typeId]?.[action.sourceActionId];
10269
+ if (tool !== void 0) actions[action.id] = tool;
10330
10270
  }
10331
- },
10332
- required: ["requestId", "result"],
10333
- additionalProperties: false
10334
- };
10335
- const RESULT_TOOL_OUTPUT_SCHEMA = {
10336
- type: "object",
10337
- properties: { recorded: {
10338
- type: "boolean",
10339
- const: true
10340
- } },
10341
- required: ["recorded"],
10342
- additionalProperties: false
10343
- };
10344
- const WRITE_OPERATION_RESULT_TOOL = {
10345
- remember: "mnemon_remember",
10346
- "supervised-writeback": "mnemon_remember",
10347
- link: "mnemon_link",
10348
- forget: "mnemon_forget",
10349
- "create-memory-body": "mnemon_memory_body_create",
10350
- "update-memory-body": "mnemon_memory_body_update",
10351
- "merge-memory-bodies": "mnemon_memory_body_merge"
10352
- };
10353
- var IdleReviewError = class extends Error {
10354
- review;
10355
- constructor(message, review, options) {
10356
- super(message, options);
10357
- this.review = review;
10358
- this.name = "IdleReviewError";
10359
10271
  }
10360
- };
10361
- /** What a memory saved without a vector means for recall, and how its vector comes. */
10362
- const UNEMBEDDED_HINT = "Saved without a vector, because embedding did not succeed: semantic matching misses it until its vector is filled in, while keyword, link and recency recall still reach it. Once the embedding service answers, the user fills it in from Test status in the Mnemon Native embedding settings, which runs through the storage root and embedding settings DSH uses; a terminal mnemon command may reach neither.";
10363
- /** The memories a write saved without a vector: Mnemon Native says so with `embedded: false` (#388). */
10364
- function withoutVector(receipts) {
10365
- return receipts.filter((receipt) => isTool(receipt.name, "mnemon_remember")).flatMap((receipt) => {
10366
- const value = optionalObject(receipt.value);
10272
+ return {
10273
+ routes,
10274
+ actions
10275
+ };
10276
+ }
10277
+ /** Explicitly pick display fields; never copy grants, authority, or opaque Source state. */
10278
+ function inspectMemoryView(generation, view, state, turn, memoryText) {
10279
+ return structuredClone({
10280
+ id: view.id,
10281
+ digest: view.digest,
10282
+ generationId: view.runtimeGeneration,
10283
+ createdAt: view.createdAt,
10284
+ state,
10285
+ ...turn === void 0 ? {} : { turn },
10286
+ strategyTypeId: view.strategyTypeId,
10287
+ strategyInstanceKey: view.strategyInstanceKey,
10288
+ extensions: view.strategyExtensions ?? [],
10289
+ projection: view.projection.map(({ id, sourceInstanceKey, mode, text, revision }) => ({
10290
+ id,
10291
+ sourceInstanceKey,
10292
+ mode,
10293
+ text,
10294
+ revision
10295
+ })),
10296
+ sourcePresentations: view.sourcePresentations ?? [],
10297
+ routes: view.routes.map(({ id, sourceInstanceKey, sourceRouteId, description, maxCalls }) => ({
10298
+ id,
10299
+ sourceInstanceKey,
10300
+ operationId: sourceRouteId,
10301
+ description,
10302
+ maxCalls
10303
+ })),
10304
+ actions: view.actionOffers.map(({ id, sourceInstanceKey, sourceActionId, description }) => ({
10305
+ id,
10306
+ sourceInstanceKey,
10307
+ operationId: sourceActionId,
10308
+ description
10309
+ })),
10310
+ memoryText: memoryText ?? presentMemoryWake(generation, view).text,
10311
+ ...view.guidance === void 0 ? {} : { guidance: view.guidance },
10312
+ diagnostics: (view.diagnostics ?? []).map((diagnostic) => diagnostic.message)
10313
+ });
10314
+ }
10315
+ //#endregion
10316
+ //#region src/host/agent-memory-turn.ts
10317
+ /** The durable log, not a parent session id, identifies the executing turn. */
10318
+ function openAgentTurn(agent) {
10319
+ let open;
10320
+ for (const event of agent.session.snapshotEvents()) {
10321
+ const turn = typeof event.data.turn === "number" ? event.data.turn : void 0;
10322
+ if (event.type === "turn/start" && turn !== void 0) open = turn;
10323
+ else if (event.type === "turn/end" && turn === open) open = void 0;
10324
+ }
10325
+ return open;
10326
+ }
10327
+ /**
10328
+ * One Agent owns each turn pin. A child additionally retains its delegation
10329
+ * across parent completion, collection, and runtime swaps until it is disposed.
10330
+ */
10331
+ var AgentMemoryTurn = class {
10332
+ agent;
10333
+ runtime;
10334
+ delegation;
10335
+ pinned;
10336
+ /** A turn that runs without memory because no composition was serving when it began. */
10337
+ unavailable;
10338
+ warned;
10339
+ pending;
10340
+ generation = 0;
10341
+ closed = false;
10342
+ lastInspection;
10343
+ lastWorkspace;
10344
+ releaseDelegation;
10345
+ constructor(agent, runtime, delegation) {
10346
+ this.agent = agent;
10347
+ this.runtime = runtime;
10348
+ this.delegation = delegation;
10349
+ if (delegation === void 0) return;
10350
+ this.releaseDelegation = runtime.executions.retain(agent, delegation);
10351
+ }
10352
+ get current() {
10353
+ return this.pinned;
10354
+ }
10355
+ /** Why the current turn has no View, when memory could not be composed for it. */
10356
+ get unavailableReason() {
10357
+ return this.unavailable?.reason;
10358
+ }
10359
+ inspect(workspaceRoot) {
10360
+ if (this.lastInspection === void 0) return void 0;
10361
+ if (workspaceRoot !== void 0 && resolve(workspaceRoot) !== this.lastWorkspace) return void 0;
10362
+ return structuredClone({
10363
+ ...this.lastInspection,
10364
+ state: this.pinned === void 0 ? "recent" : "active"
10365
+ });
10366
+ }
10367
+ clearInspection() {
10368
+ this.lastInspection = void 0;
10369
+ this.lastWorkspace = void 0;
10370
+ }
10371
+ /** Capture now; descendants must not resolve a later parent turn on demand. */
10372
+ delegate() {
10373
+ if (this.closed) throw new Error("memory Agent lifetime has ended");
10374
+ if (this.pinned !== void 0) return {
10375
+ graph: this.pinned.graph,
10376
+ scope: this.pinned.context.scope,
10377
+ viewId: this.pinned.context.view.id
10378
+ };
10379
+ if (this.delegation !== void 0) return this.delegation;
10380
+ const graph = this.runtime.forAgent(this.agent);
10381
+ return {
10382
+ graph,
10383
+ scope: agentScope(this.agent, graph.config)
10384
+ };
10385
+ }
10386
+ async begin(turn, signal) {
10387
+ if (this.closed) throw new Error("memory Agent lifetime has ended");
10388
+ signal?.throwIfAborted();
10389
+ if (this.pinned?.turn === turn || this.unavailable?.turn === turn) return;
10390
+ if (this.pending?.turn === turn) return this.pending.result;
10391
+ this.end();
10392
+ const generation = this.generation;
10393
+ const controller = new AbortController();
10394
+ const abort = () => controller.abort(signal.reason);
10395
+ signal?.addEventListener("abort", abort, { once: true });
10396
+ const result = this.pin(turn, generation, controller.signal).catch((error) => {
10397
+ if (!isMemoryCompositionUnavailable(error) || this.closed || this.generation !== generation) throw error;
10398
+ this.unavailable = {
10399
+ turn,
10400
+ reason: error.message
10401
+ };
10402
+ this.clearInspection();
10403
+ if (this.warned !== error.message) console.warn(`[dsh-mnemon] this turn runs without memory: ${error.message}`);
10404
+ this.warned = error.message;
10405
+ });
10406
+ this.pending = {
10407
+ turn,
10408
+ generation,
10409
+ controller,
10410
+ result
10411
+ };
10412
+ try {
10413
+ await result;
10414
+ } finally {
10415
+ signal?.removeEventListener("abort", abort);
10416
+ if (this.pending?.generation === generation) this.pending = void 0;
10417
+ }
10418
+ }
10419
+ end(turn) {
10420
+ if (turn !== void 0 && this.pinned?.turn !== turn && this.pending?.turn !== turn && this.unavailable?.turn !== turn) return;
10421
+ this.generation += 1;
10422
+ this.unavailable = void 0;
10423
+ this.pending?.controller.abort(/* @__PURE__ */ new Error("memory turn ended during View preparation"));
10424
+ this.pending = void 0;
10425
+ const pinned = this.pinned;
10426
+ this.pinned = void 0;
10427
+ pinned?.release();
10428
+ }
10429
+ dispose() {
10430
+ if (this.closed) return;
10431
+ this.closed = true;
10432
+ this.clearInspection();
10433
+ try {
10434
+ this.end();
10435
+ } finally {
10436
+ this.releaseDelegation?.();
10437
+ }
10438
+ }
10439
+ async pin(turn, generation, signal) {
10440
+ const execution = await this.runtime.executions.turn(this.agent, turn, signal, this.delegation);
10441
+ const { graph, context } = execution;
10442
+ try {
10443
+ if (this.closed || this.generation !== generation) throw new Error("memory turn ended during View preparation");
10444
+ signal?.throwIfAborted();
10445
+ const inspection = inspectMemoryView(graph.memoryComposition.generation(context.view.runtimeGeneration), context.view, "active", turn, modelMemoryWake(graph, context).text);
10446
+ this.pinned = {
10447
+ turn,
10448
+ graph,
10449
+ context,
10450
+ release: execution.release
10451
+ };
10452
+ this.warned = void 0;
10453
+ this.lastInspection = inspection;
10454
+ this.lastWorkspace = context.scope.workspaceId === void 0 ? void 0 : resolve(context.scope.workspaceId);
10455
+ } catch (error) {
10456
+ execution.release();
10457
+ throw error;
10458
+ }
10459
+ }
10460
+ };
10461
+ //#endregion
10462
+ //#region src/host/tool-execution.ts
10463
+ /** DSH 0.2.2 exposes executor registration and batch dispatch together. */
10464
+ function usesToolBatchExecution(tools) {
10465
+ return typeof tools.setExecutor === "function";
10466
+ }
10467
+ /** Preserve the Host's scoped executor, guards, policy and final result for one call. */
10468
+ async function executeHostTool(tools, input) {
10469
+ if (tools.execute === void 0) throw new Error("The DSH tool execution service is unavailable");
10470
+ if (!usesToolBatchExecution(tools)) return tools.execute(input);
10471
+ const results = (await tools.execute({
10472
+ calls: [{
10473
+ type: "tool-call",
10474
+ id: input.callId,
10475
+ name: input.name,
10476
+ arguments: JSON.stringify(input.arguments)
10477
+ }],
10478
+ agent: input.agent,
10479
+ signal: input.signal
10480
+ }))?.results;
10481
+ const result = results?.[0];
10482
+ if (!Array.isArray(results) || results.length !== 1 || result?.callId !== input.callId || typeof result.result?.isError !== "boolean") throw new Error("The DSH tool batch did not return the requested call outcome");
10483
+ return result.result;
10484
+ }
10485
+ //#endregion
10486
+ //#region src/host/review-admission.ts
10487
+ const RESULT_TOOL = "mnemon_subagent_result";
10488
+ const CHECK_TIMEOUT_MS = 5e3;
10489
+ /** An in-turn no-op challenge through the mandatory review completion channel.
10490
+ * Inventory alone cannot see execution-time guards. No errors are classified as
10491
+ * permission failures, and an unavailable checkpoint is never a permanent ban.
10492
+ */
10493
+ var AutomaticReviewAdmission = class {
10494
+ challenges = /* @__PURE__ */ new Map();
10495
+ controllers = /* @__PURE__ */ new Set();
10496
+ draining = /* @__PURE__ */ new WeakSet();
10497
+ disposed = false;
10498
+ /** Only an unexpired, host-issued challenge for this exact Agent can no-op. */
10499
+ acknowledge(args, execution) {
10500
+ if (typeof args !== "object" || args === null || !("requestId" in args) || typeof args.requestId !== "string") return false;
10501
+ const challenge = this.challenges.get(args.requestId);
10502
+ if (challenge === void 0 || challenge.agent !== execution.agent || challenge.execution !== void 0) return false;
10503
+ execution.signal.throwIfAborted();
10504
+ challenge.execution = execution;
10505
+ return true;
10506
+ }
10507
+ dispose() {
10508
+ this.disposed = true;
10509
+ this.challenges.clear();
10510
+ for (const controller of this.controllers) controller.abort();
10511
+ }
10512
+ async check(agent, signal) {
10513
+ signal.throwIfAborted();
10514
+ const tools = agent.ctx.tools;
10515
+ if (this.disposed || tools?.execute === void 0 || this.draining.has(agent) || openAgentTurn(agent) === void 0) return false;
10516
+ const controller = new AbortController();
10517
+ this.controllers.add(controller);
10518
+ const combined = AbortSignal.any([signal, controller.signal]);
10519
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
10520
+ timeout.unref?.();
10521
+ const attempt = async (codeMode) => {
10522
+ combined.throwIfAborted();
10523
+ const requestId = randomUUID();
10524
+ const callId = randomUUID();
10525
+ const challenge = {
10526
+ agent,
10527
+ accepted: false
10528
+ };
10529
+ this.challenges.set(requestId, challenge);
10530
+ const revoke = () => this.challenges.delete(requestId);
10531
+ combined.addEventListener("abort", revoke, { once: true });
10532
+ const stops = [];
10533
+ let executing = false;
10534
+ const cleanup = () => {
10535
+ for (const stop of stops.reverse()) stop();
10536
+ this.draining.delete(agent);
10537
+ };
10538
+ try {
10539
+ stops.push(agent.ctx.on("tools/result", ((execution, result) => {
10540
+ if (execution === challenge.execution) challenge.accepted = result.isError === false;
10541
+ })));
10542
+ stops.push(agent.ctx.on("tools/pre-execute", (async (execution, next) => {
10543
+ const decision = await next();
10544
+ return execution.agent === agent && (execution.callId === callId || execution.rootCallId === callId) && decision.kind === "ask" ? { kind: "cancel" } : decision;
10545
+ }), { prepend: true }));
10546
+ const args = {
10547
+ requestId,
10548
+ result: {}
10549
+ };
10550
+ const pending = executeHostTool(tools, {
10551
+ callId,
10552
+ agent,
10553
+ signal: combined,
10554
+ name: codeMode ? "run_code" : RESULT_TOOL,
10555
+ arguments: codeMode ? {
10556
+ description: "Check automatic memory review availability",
10557
+ code: `await tools.mnemon_subagent_result(${JSON.stringify(args)})`
10558
+ } : args
10559
+ });
10560
+ executing = true;
10561
+ this.draining.add(agent);
10562
+ const outcome = await pending.finally(cleanup);
10563
+ combined.throwIfAborted();
10564
+ return challenge.execution !== void 0 && challenge.accepted && outcome.isError === false;
10565
+ } finally {
10566
+ this.challenges.delete(requestId);
10567
+ combined.removeEventListener("abort", revoke);
10568
+ if (!executing) cleanup();
10569
+ }
10570
+ };
10571
+ try {
10572
+ if (await attempt(false)) return true;
10573
+ return (usesToolBatchExecution(tools) || tools.get?.("run_code", agent) !== void 0) && await attempt(true);
10574
+ } catch {
10575
+ signal.throwIfAborted();
10576
+ return false;
10577
+ } finally {
10578
+ clearTimeout(timeout);
10579
+ this.controllers.delete(controller);
10580
+ }
10581
+ }
10582
+ };
10583
+ //#endregion
10584
+ //#region src/host/subagent.ts
10585
+ function evidenceInsights(evidence) {
10586
+ return evidence.items.map((item) => {
10587
+ return {
10588
+ ...optionalObject(item.provenance) ?? {},
10589
+ id: item.id,
10590
+ content: item.text,
10591
+ ...item.score === void 0 ? {} : { score: item.score },
10592
+ ...item.revision === void 0 ? {} : { revision: item.revision }
10593
+ };
10594
+ });
10595
+ }
10596
+ /**
10597
+ * Every task dsh-mnemon delegates to a child Agent starts with one line of its own, such as
10598
+ * `[dsh-mnemon:delegated review]` (#393). DSH records the task as the child's prompt from a
10599
+ * person, so a plugin that reads sessions, such as one that collects a workspace's work, can
10600
+ * rely on this line, documented as stable, to leave Mnemon's own children out.
10601
+ */
10602
+ const DELEGATED_TASK_PREFIX = "[dsh-mnemon:delegated ";
10603
+ function delegatedTaskLine(operation) {
10604
+ return `${DELEGATED_TASK_PREFIX}${operation}]`;
10605
+ }
10606
+ const WRITE_TOOLS = [
10607
+ ...[
10608
+ "mnemon_memory_bodies",
10609
+ "mnemon_recall",
10610
+ "mnemon_related"
10611
+ ],
10612
+ "mnemon_remember",
10613
+ "mnemon_link",
10614
+ "mnemon_forget",
10615
+ "mnemon_memory_body_create",
10616
+ "mnemon_memory_body_update",
10617
+ "mnemon_memory_body_merge"
10618
+ ];
10619
+ const AUTONOMOUS_WRITE_TOOLS = WRITE_TOOLS.filter((tool) => tool !== "mnemon_forget");
10620
+ const EXPLICIT_WRITE_TOOLS = WRITE_TOOLS;
10621
+ const REVIEW_TOOLS = [
10622
+ ...["mnemon_document_search"],
10623
+ "mnemon_runtime_memory",
10624
+ "mnemon_document_create"
10625
+ ];
10626
+ const RESULT_TOOL_NAME = "mnemon_subagent_result";
10627
+ /** How many finished delegated children stay refused memory tools if DSH resumes one. */
10628
+ const FINISHED_CHILDREN = 1e3;
10629
+ const FINISHED_CHILD_REFUSAL = "This Mnemon task has finished, so it no longer reads or changes memory. Reply without memory tools.";
10630
+ const RESULT_TOOL_INPUT_SCHEMA = {
10631
+ type: "object",
10632
+ properties: {
10633
+ requestId: {
10634
+ type: "string",
10635
+ minLength: 1
10636
+ },
10637
+ result: {
10638
+ type: "object",
10639
+ additionalProperties: true
10640
+ }
10641
+ },
10642
+ required: ["requestId", "result"],
10643
+ additionalProperties: false
10644
+ };
10645
+ const RESULT_TOOL_OUTPUT_SCHEMA = {
10646
+ type: "object",
10647
+ properties: { recorded: {
10648
+ type: "boolean",
10649
+ const: true
10650
+ } },
10651
+ required: ["recorded"],
10652
+ additionalProperties: false
10653
+ };
10654
+ const WRITE_OPERATION_RESULT_TOOL = {
10655
+ remember: "mnemon_remember",
10656
+ "supervised-writeback": "mnemon_remember",
10657
+ link: "mnemon_link",
10658
+ forget: "mnemon_forget",
10659
+ "create-memory-body": "mnemon_memory_body_create",
10660
+ "update-memory-body": "mnemon_memory_body_update",
10661
+ "merge-memory-bodies": "mnemon_memory_body_merge"
10662
+ };
10663
+ var IdleReviewError = class extends Error {
10664
+ review;
10665
+ constructor(message, review, options) {
10666
+ super(message, options);
10667
+ this.review = review;
10668
+ this.name = "IdleReviewError";
10669
+ }
10670
+ };
10671
+ /** What a memory saved without a vector means for recall, and how its vector comes. */
10672
+ const UNEMBEDDED_HINT = "Saved without a vector, because embedding did not succeed: semantic matching misses it until its vector is filled in, while keyword, link and recency recall still reach it. Once the embedding service answers, the user fills it in from Test status in the Mnemon Native embedding settings, which runs through the storage root and embedding settings DSH uses; a terminal mnemon command may reach neither.";
10673
+ /** The memories a write saved without a vector: Mnemon Native says so with `embedded: false` (#388). */
10674
+ function withoutVector(receipts) {
10675
+ return receipts.filter((receipt) => isTool(receipt.name, "mnemon_remember")).flatMap((receipt) => {
10676
+ const value = optionalObject(receipt.value);
10367
10677
  if (value?.embedded !== false) return [];
10368
10678
  return [{
10369
10679
  ...typeof value.memoryBodyId === "string" ? { memoryBodyId: value.memoryBodyId } : {},
@@ -11442,7 +11752,7 @@ function bounded(value, maximum) {
11442
11752
  if (typeof value !== "string") return void 0;
11443
11753
  const normalized = value.replace(/\s+/gu, " ").trim();
11444
11754
  if (normalized === "") return void 0;
11445
- return normalized.length <= maximum ? normalized : normalized.slice(0, maximum - 1) + "…";
11755
+ return truncate(normalized, maximum);
11446
11756
  }
11447
11757
  function item(value, index) {
11448
11758
  const entry = object$5(value);
@@ -11573,361 +11883,96 @@ function counter(name) {
11573
11883
  return tool.sourceTypeId === "documents" ? "documentSearches" : "recalls";
11574
11884
  }
11575
11885
  function eventTurn$1(event) {
11576
- return typeof event.data.turn === "number" ? event.data.turn : void 0;
11577
- }
11578
- function resultCallId(event) {
11579
- const message = event.data.message;
11580
- return typeof message?.source?.callId === "string" && message.source.callId !== "" ? message.source.callId : void 0;
11581
- }
11582
- function emptyActivity(turn) {
11583
- return {
11584
- turn,
11585
- count: 0,
11586
- names: [],
11587
- recalls: 0,
11588
- writes: 0,
11589
- documentSearches: 0,
11590
- inspections: 0,
11591
- failures: 0,
11592
- retrieved: [],
11593
- writebacks: []
11594
- };
11595
- }
11596
- /**
11597
- * Incremental durable-log projection. Repeated UI reads process only events
11598
- * appended since the previous snapshot instead of rescanning the full session.
11599
- */
11600
- var TurnActivityProjection = class {
11601
- eventCount = 0;
11602
- lastEventSeq;
11603
- pending = /* @__PURE__ */ new Map();
11604
- byTurn = /* @__PURE__ */ new Map();
11605
- reset() {
11606
- this.eventCount = 0;
11607
- this.lastEventSeq = void 0;
11608
- this.pending.clear();
11609
- this.byTurn.clear();
11610
- }
11611
- snapshot(events) {
11612
- const currentLastSeq = events.at(-1)?.seq;
11613
- if (events.length < this.eventCount || events.length === this.eventCount && this.lastEventSeq !== currentLastSeq) this.reset();
11614
- for (let index = this.eventCount; index < events.length; index += 1) this.consume(events[index]);
11615
- this.eventCount = events.length;
11616
- this.lastEventSeq = currentLastSeq;
11617
- return {
11618
- cursor: typeof currentLastSeq === "number" ? currentLastSeq : events.length,
11619
- activities: [...this.byTurn.values()].sort((left, right) => left.turn - right.turn).map((activity) => ({
11620
- ...activity,
11621
- names: [...activity.names],
11622
- retrieved: activity.retrieved.map((read) => ({
11623
- ...read,
11624
- items: read.items.map((item) => ({ ...item }))
11625
- })),
11626
- writebacks: activity.writebacks.map((write) => ({
11627
- ...write,
11628
- item: { ...write.item }
11629
- }))
11630
- }))
11631
- };
11632
- }
11633
- consume(event) {
11634
- if (event.type === "tool/call") {
11635
- const turn = eventTurn$1(event);
11636
- const callId = event.data.callId;
11637
- const name = event.data.name;
11638
- if (turn !== void 0 && typeof callId === "string" && typeof name === "string" && name.startsWith("mnemon_")) this.pending.set(callId, {
11639
- turn,
11640
- name
11641
- });
11642
- return;
11643
- }
11644
- if (event.type !== "tool/result") return;
11645
- const callId = resultCallId(event);
11646
- if (callId === void 0) return;
11647
- const call = this.pending.get(callId);
11648
- if (call === void 0) return;
11649
- this.pending.delete(callId);
11650
- let activity = this.byTurn.get(call.turn);
11651
- if (activity === void 0) {
11652
- activity = emptyActivity(call.turn);
11653
- this.byTurn.set(call.turn, activity);
11654
- }
11655
- activity.count += 1;
11656
- activity.names.push(call.name);
11657
- if (event.data.error !== void 0) {
11658
- activity.failures += 1;
11659
- return;
11660
- }
11661
- const semantic = parseMnemonActivityMeta(event.data.meta, callId, call.name);
11662
- if (semantic.read !== void 0) activity.retrieved.push(semantic.read);
11663
- if (semantic.writeback !== void 0) activity.writebacks.push(semantic.writeback);
11664
- const counted = counter(call.name);
11665
- if (counted !== void 0) activity[counted] += 1;
11666
- }
11667
- };
11668
- //#endregion
11669
- //#region src/host/view-presentation.ts
11670
- /**
11671
- * The named tools bound to each Source type's Routes or Actions, from the tool table: these are
11672
- * existing DSH product-tool bindings, not Core operation names. Tools that share one operation are
11673
- * listed together, in table order, as the View's tool list names them.
11674
- */
11675
- function namedBindings(kind) {
11676
- const bindings = {};
11677
- for (const [name, tool] of memoryTools()) {
11678
- if (tool.sourceTypeId === void 0 || tool.operation === void 0 || tool.kind === "write" !== (kind === "action")) continue;
11679
- const operations = bindings[tool.sourceTypeId] ??= {};
11680
- const listed = operations[tool.operation];
11681
- operations[tool.operation] = listed === void 0 ? name : `${listed} / ${name}`;
11682
- }
11683
- return bindings;
11684
- }
11685
- const ROUTE_TOOLS = namedBindings("route");
11686
- const ACTION_TOOLS = namedBindings("action");
11687
- const NAMED_SOURCE_TYPES = [.../* @__PURE__ */ new Set([...Object.keys(ROUTE_TOOLS), ...Object.keys(ACTION_TOOLS)])];
11688
- /** Use a short availability list only where the Host has the matching named binding.
11689
- * Unbound external operations retain exact ids and schemas in the generic envelope. */
11690
- function modelMemoryWake(graph, turn) {
11691
- const generation = graph.memoryComposition.generation(turn.view.runtimeGeneration);
11692
- if (generation === void 0) throw new Error("Cannot present an unpinned Memory generation");
11693
- return graph.composableTurns.memoryWake(turn.view.id, memoryWakeBindings(generation, turn.view));
11694
- }
11695
- function presentMemoryWake(generation, view) {
11696
- return createMemoryWake(view, memoryWakeBindings(generation, view));
11697
- }
11698
- /**
11699
- * Why a generic View tool names no id this turn's View offers, told apart (#390); undefined when
11700
- * the id is offered. It names only what the MNEMON VIEW ROUTES envelope shows: an operation bound
11701
- * to a named tool is offered through that tool, and the envelope does not list its id.
11702
- */
11703
- function unofferedViewId(graph, turn, kind, id) {
11704
- const view = turn.view;
11705
- const offered = kind === "route" ? view.routes.map((route) => ({
11706
- id: route.id,
11707
- operation: route.sourceRouteId
11708
- })) : view.actionOffers.map((offer) => ({
11709
- id: offer.id,
11710
- operation: offer.sourceActionId
11711
- }));
11712
- const label = kind === "route" ? "memory View Route" : "memory ActionOffer";
11713
- if (typeof id !== "string" || id === "") return `${label} needs ${kind === "route" ? "routeId" : "offerId"}, the exact id of one it offers`;
11714
- if (offered.some((entry) => entry.id === id)) return void 0;
11715
- const generation = graph.memoryComposition.generation(view.runtimeGeneration);
11716
- const bindings = generation === void 0 ? {} : memoryWakeBindings(generation, view);
11717
- const named = (kind === "route" ? bindings.routes : bindings.actions) ?? {};
11718
- const short = id.split("/").at(-1);
11719
- const tools = [...new Set(offered.filter((entry) => named[entry.id] !== void 0 && (entry.operation === short || entry.id.split("/").at(-1) === short)).map((entry) => named[entry.id]))];
11720
- if (tools.length > 0) return `${label} is unavailable: ${id}; this View offers it as ${tools.join(" and ")}, so call that tool`;
11721
- const shown = offered.filter((entry) => named[entry.id] === void 0).map((entry) => entry.id);
11722
- if (shown.length === 0) return `${label} is unavailable: ${id}; MNEMON VIEW ROUTES offers none by id this turn, only the named tools in MNEMON VIEW TOOLS`;
11723
- const near = shown.filter((candidate) => candidate.split("/").at(-1) === short);
11724
- if (near.length === 1) return `${label} is unavailable: ${id}; ids are exact as MNEMON VIEW ROUTES lists them: ${JSON.stringify(near[0])}`;
11725
- const listed = [...near, ...shown.filter((candidate) => !near.includes(candidate))];
11726
- return `${label} is unavailable: ${id}; MNEMON VIEW ROUTES offers ${listed.slice(0, 12).map((candidate) => JSON.stringify(candidate)).join(", ")}${listed.length > 12 ? ` and ${listed.length - 12} more` : ""}`;
11727
- }
11728
- function memoryWakeBindings(generation, view) {
11729
- const instances = generation.sourceInstances();
11730
- const routes = {}, actions = {};
11731
- for (const typeId of NAMED_SOURCE_TYPES) {
11732
- const selected = namedToolInstance(instances.filter((source) => source.sourceTypeId === typeId), typeId);
11733
- if (selected === void 0) continue;
11734
- for (const route of view.routes.filter((route) => route.sourceInstanceKey === selected.sourceInstanceKey)) {
11735
- if (view.readGrants.find((grant) => grant.id === route.readGrantId)?.schema !== `dsh-mnemon.${typeId}/v1`) continue;
11736
- const tool = ROUTE_TOOLS[typeId]?.[route.sourceRouteId];
11737
- if (tool !== void 0) routes[route.id] = tool;
11738
- }
11739
- for (const action of view.actionOffers.filter((action) => action.sourceInstanceKey === selected.sourceInstanceKey)) {
11740
- const tool = ACTION_TOOLS[typeId]?.[action.sourceActionId];
11741
- if (tool !== void 0) actions[action.id] = tool;
11742
- }
11743
- }
11744
- return {
11745
- routes,
11746
- actions
11747
- };
11748
- }
11749
- /** Explicitly pick display fields; never copy grants, authority, or opaque Source state. */
11750
- function inspectMemoryView(generation, view, state, turn, memoryText) {
11751
- return structuredClone({
11752
- id: view.id,
11753
- digest: view.digest,
11754
- generationId: view.runtimeGeneration,
11755
- createdAt: view.createdAt,
11756
- state,
11757
- ...turn === void 0 ? {} : { turn },
11758
- strategyTypeId: view.strategyTypeId,
11759
- strategyInstanceKey: view.strategyInstanceKey,
11760
- extensions: view.strategyExtensions ?? [],
11761
- projection: view.projection.map(({ id, sourceInstanceKey, mode, text, revision }) => ({
11762
- id,
11763
- sourceInstanceKey,
11764
- mode,
11765
- text,
11766
- revision
11767
- })),
11768
- sourcePresentations: view.sourcePresentations ?? [],
11769
- routes: view.routes.map(({ id, sourceInstanceKey, sourceRouteId, description, maxCalls }) => ({
11770
- id,
11771
- sourceInstanceKey,
11772
- operationId: sourceRouteId,
11773
- description,
11774
- maxCalls
11775
- })),
11776
- actions: view.actionOffers.map(({ id, sourceInstanceKey, sourceActionId, description }) => ({
11777
- id,
11778
- sourceInstanceKey,
11779
- operationId: sourceActionId,
11780
- description
11781
- })),
11782
- memoryText: memoryText ?? presentMemoryWake(generation, view).text,
11783
- ...view.guidance === void 0 ? {} : { guidance: view.guidance },
11784
- diagnostics: (view.diagnostics ?? []).map((diagnostic) => diagnostic.message)
11785
- });
11886
+ return typeof event.data.turn === "number" ? event.data.turn : void 0;
11786
11887
  }
11787
- //#endregion
11788
- //#region src/host/agent-memory-turn.ts
11789
- /** The durable log, not a parent session id, identifies the executing turn. */
11790
- function openAgentTurn(agent) {
11791
- let open;
11792
- for (const event of agent.session.snapshotEvents()) {
11793
- const turn = typeof event.data.turn === "number" ? event.data.turn : void 0;
11794
- if (event.type === "turn/start" && turn !== void 0) open = turn;
11795
- else if (event.type === "turn/end" && turn === open) open = void 0;
11796
- }
11797
- return open;
11888
+ function resultCallId(event) {
11889
+ const message = event.data.message;
11890
+ return typeof message?.source?.callId === "string" && message.source.callId !== "" ? message.source.callId : void 0;
11891
+ }
11892
+ function emptyActivity(turn) {
11893
+ return {
11894
+ turn,
11895
+ count: 0,
11896
+ names: [],
11897
+ recalls: 0,
11898
+ writes: 0,
11899
+ documentSearches: 0,
11900
+ inspections: 0,
11901
+ failures: 0,
11902
+ retrieved: [],
11903
+ writebacks: []
11904
+ };
11798
11905
  }
11799
11906
  /**
11800
- * One Agent owns each turn pin. A child additionally retains its delegation
11801
- * across parent completion, collection, and runtime swaps until it is disposed.
11907
+ * Incremental durable-log projection. Repeated UI reads process only events
11908
+ * appended since the previous snapshot instead of rescanning the full session.
11802
11909
  */
11803
- var AgentMemoryTurn = class {
11804
- agent;
11805
- runtime;
11806
- delegation;
11807
- pinned;
11808
- /** A turn that runs without memory because no composition was serving when it began. */
11809
- unavailable;
11810
- warned;
11811
- pending;
11812
- generation = 0;
11813
- closed = false;
11814
- lastInspection;
11815
- lastWorkspace;
11816
- releaseDelegation;
11817
- constructor(agent, runtime, delegation) {
11818
- this.agent = agent;
11819
- this.runtime = runtime;
11820
- this.delegation = delegation;
11821
- if (delegation === void 0) return;
11822
- this.releaseDelegation = runtime.executions.retain(agent, delegation);
11823
- }
11824
- get current() {
11825
- return this.pinned;
11826
- }
11827
- /** Why the current turn has no View, when memory could not be composed for it. */
11828
- get unavailableReason() {
11829
- return this.unavailable?.reason;
11830
- }
11831
- inspect(workspaceRoot) {
11832
- if (this.lastInspection === void 0) return void 0;
11833
- if (workspaceRoot !== void 0 && resolve(workspaceRoot) !== this.lastWorkspace) return void 0;
11834
- return structuredClone({
11835
- ...this.lastInspection,
11836
- state: this.pinned === void 0 ? "recent" : "active"
11837
- });
11838
- }
11839
- clearInspection() {
11840
- this.lastInspection = void 0;
11841
- this.lastWorkspace = void 0;
11910
+ var TurnActivityProjection = class {
11911
+ eventCount = 0;
11912
+ lastEventSeq;
11913
+ pending = /* @__PURE__ */ new Map();
11914
+ byTurn = /* @__PURE__ */ new Map();
11915
+ reset() {
11916
+ this.eventCount = 0;
11917
+ this.lastEventSeq = void 0;
11918
+ this.pending.clear();
11919
+ this.byTurn.clear();
11842
11920
  }
11843
- /** Capture now; descendants must not resolve a later parent turn on demand. */
11844
- delegate() {
11845
- if (this.closed) throw new Error("memory Agent lifetime has ended");
11846
- if (this.pinned !== void 0) return {
11847
- graph: this.pinned.graph,
11848
- scope: this.pinned.context.scope,
11849
- viewId: this.pinned.context.view.id
11850
- };
11851
- if (this.delegation !== void 0) return this.delegation;
11852
- const graph = this.runtime.forAgent(this.agent);
11921
+ snapshot(events) {
11922
+ const currentLastSeq = events.at(-1)?.seq;
11923
+ if (events.length < this.eventCount || events.length === this.eventCount && this.lastEventSeq !== currentLastSeq) this.reset();
11924
+ for (let index = this.eventCount; index < events.length; index += 1) this.consume(events[index]);
11925
+ this.eventCount = events.length;
11926
+ this.lastEventSeq = currentLastSeq;
11853
11927
  return {
11854
- graph,
11855
- scope: agentScope(this.agent, graph.config)
11928
+ cursor: typeof currentLastSeq === "number" ? currentLastSeq : events.length,
11929
+ activities: [...this.byTurn.values()].sort((left, right) => left.turn - right.turn).map((activity) => ({
11930
+ ...activity,
11931
+ names: [...activity.names],
11932
+ retrieved: activity.retrieved.map((read) => ({
11933
+ ...read,
11934
+ items: read.items.map((item) => ({ ...item }))
11935
+ })),
11936
+ writebacks: activity.writebacks.map((write) => ({
11937
+ ...write,
11938
+ item: { ...write.item }
11939
+ }))
11940
+ }))
11856
11941
  };
11857
11942
  }
11858
- async begin(turn, signal) {
11859
- if (this.closed) throw new Error("memory Agent lifetime has ended");
11860
- signal?.throwIfAborted();
11861
- if (this.pinned?.turn === turn || this.unavailable?.turn === turn) return;
11862
- if (this.pending?.turn === turn) return this.pending.result;
11863
- this.end();
11864
- const generation = this.generation;
11865
- const controller = new AbortController();
11866
- const abort = () => controller.abort(signal.reason);
11867
- signal?.addEventListener("abort", abort, { once: true });
11868
- const result = this.pin(turn, generation, controller.signal).catch((error) => {
11869
- if (!isMemoryCompositionUnavailable(error) || this.closed || this.generation !== generation) throw error;
11870
- this.unavailable = {
11943
+ consume(event) {
11944
+ if (event.type === "tool/call") {
11945
+ const turn = eventTurn$1(event);
11946
+ const callId = event.data.callId;
11947
+ const name = event.data.name;
11948
+ if (turn !== void 0 && typeof callId === "string" && typeof name === "string" && name.startsWith("mnemon_")) this.pending.set(callId, {
11871
11949
  turn,
11872
- reason: error.message
11873
- };
11874
- this.clearInspection();
11875
- if (this.warned !== error.message) console.warn(`[dsh-mnemon] this turn runs without memory: ${error.message}`);
11876
- this.warned = error.message;
11877
- });
11878
- this.pending = {
11879
- turn,
11880
- generation,
11881
- controller,
11882
- result
11883
- };
11884
- try {
11885
- await result;
11886
- } finally {
11887
- signal?.removeEventListener("abort", abort);
11888
- if (this.pending?.generation === generation) this.pending = void 0;
11950
+ name
11951
+ });
11952
+ return;
11889
11953
  }
11890
- }
11891
- end(turn) {
11892
- if (turn !== void 0 && this.pinned?.turn !== turn && this.pending?.turn !== turn && this.unavailable?.turn !== turn) return;
11893
- this.generation += 1;
11894
- this.unavailable = void 0;
11895
- this.pending?.controller.abort(/* @__PURE__ */ new Error("memory turn ended during View preparation"));
11896
- this.pending = void 0;
11897
- const pinned = this.pinned;
11898
- this.pinned = void 0;
11899
- pinned?.release();
11900
- }
11901
- dispose() {
11902
- if (this.closed) return;
11903
- this.closed = true;
11904
- this.clearInspection();
11905
- try {
11906
- this.end();
11907
- } finally {
11908
- this.releaseDelegation?.();
11954
+ if (event.type !== "tool/result") return;
11955
+ const callId = resultCallId(event);
11956
+ if (callId === void 0) return;
11957
+ const call = this.pending.get(callId);
11958
+ if (call === void 0) return;
11959
+ this.pending.delete(callId);
11960
+ let activity = this.byTurn.get(call.turn);
11961
+ if (activity === void 0) {
11962
+ activity = emptyActivity(call.turn);
11963
+ this.byTurn.set(call.turn, activity);
11909
11964
  }
11910
- }
11911
- async pin(turn, generation, signal) {
11912
- const execution = await this.runtime.executions.turn(this.agent, turn, signal, this.delegation);
11913
- const { graph, context } = execution;
11914
- try {
11915
- if (this.closed || this.generation !== generation) throw new Error("memory turn ended during View preparation");
11916
- signal?.throwIfAborted();
11917
- const inspection = inspectMemoryView(graph.memoryComposition.generation(context.view.runtimeGeneration), context.view, "active", turn, modelMemoryWake(graph, context).text);
11918
- this.pinned = {
11919
- turn,
11920
- graph,
11921
- context,
11922
- release: execution.release
11923
- };
11924
- this.warned = void 0;
11925
- this.lastInspection = inspection;
11926
- this.lastWorkspace = context.scope.workspaceId === void 0 ? void 0 : resolve(context.scope.workspaceId);
11927
- } catch (error) {
11928
- execution.release();
11929
- throw error;
11965
+ activity.count += 1;
11966
+ activity.names.push(call.name);
11967
+ if (event.data.error !== void 0) {
11968
+ activity.failures += 1;
11969
+ return;
11930
11970
  }
11971
+ const semantic = parseMnemonActivityMeta(event.data.meta, callId, call.name);
11972
+ if (semantic.read !== void 0) activity.retrieved.push(semantic.read);
11973
+ if (semantic.writeback !== void 0) activity.writebacks.push(semantic.writeback);
11974
+ const counted = counter(call.name);
11975
+ if (counted !== void 0) activity[counted] += 1;
11931
11976
  }
11932
11977
  };
11933
11978
  //#endregion
@@ -12327,6 +12372,7 @@ var MnemonAgentLifecycle = class {
12327
12372
  this.releaseView(payload.turn);
12328
12373
  return decision;
12329
12374
  }
12375
+ if (!this.disposed && openAgentTurn(this.agent) === payload.turn) await this.memoryTurn?.begin(payload.turn, payload.signal);
12330
12376
  if (!this.config.lifecycleEnabled) return decision;
12331
12377
  if (this.config.writeEnabled && this.config.writebackMode === "guided") this.recordTurnMessages(payload.turn, decision.messages);
12332
12378
  if (payload.step !== 1) return decision;
@@ -12370,7 +12416,35 @@ var MnemonAgentLifecycle = class {
12370
12416
  this.memoryTurn?.end(turn);
12371
12417
  }
12372
12418
  async finishTurn(payload) {
12373
- this.scheduleIdleReview(payload.turn);
12419
+ this.cancelIdleReview(true);
12420
+ this.recordComposedTurn(payload.turn);
12421
+ if (payload.signal.aborted || !this.restoreWatermark() || !this.idleReviewAllowed()) return;
12422
+ const activity = this.ensureTurnActivity(payload.turn);
12423
+ const tools = completedToolActivity(this.agent.session.snapshotEvents(), payload.turn);
12424
+ activity.toolCallCount = tools.count;
12425
+ activity.toolNames = tools.names;
12426
+ if (!this.reviewActivity().eligible || !this.reviewAdmitted(payload.turn)) return;
12427
+ const controller = new AbortController();
12428
+ const signal = AbortSignal.any([payload.signal, controller.signal]);
12429
+ const epoch = this.reviewEpoch;
12430
+ const policy = this.reviewPolicy();
12431
+ this.reviewController = controller;
12432
+ this.reviewRunning = true;
12433
+ let admitted = false;
12434
+ try {
12435
+ const available = await this.coordinator.checkReviewAccess(this.agent, signal);
12436
+ if (signal.aborted || epoch !== this.reviewEpoch || policy !== this.reviewPolicy() || !this.idleReviewAllowed()) return;
12437
+ this.reviewAccessBlocked = !available;
12438
+ admitted = available;
12439
+ } catch (error) {
12440
+ if (!signal.aborted) this.fail(error);
12441
+ } finally {
12442
+ if (this.reviewController === controller) {
12443
+ this.reviewRunning = false;
12444
+ this.reviewController = void 0;
12445
+ }
12446
+ }
12447
+ if (admitted) this.scheduleIdleReview(payload.turn);
12374
12448
  }
12375
12449
  sessionEvent(session, event) {
12376
12450
  if (session !== this.agent.session || event.type !== "turn/end") return;
@@ -12381,12 +12455,7 @@ var MnemonAgentLifecycle = class {
12381
12455
  }
12382
12456
  scheduleIdleReview(turn) {
12383
12457
  this.cancelIdleReview(true);
12384
- this.recordComposedTurn(turn);
12385
12458
  if (!this.restoreWatermark() || !this.idleReviewAllowed()) return;
12386
- const activity = this.ensureTurnActivity(turn);
12387
- const tools = completedToolActivity(this.agent.session.snapshotEvents(), turn);
12388
- activity.toolCallCount = tools.count;
12389
- activity.toolNames = tools.names;
12390
12459
  if (!this.reviewActivity().eligible || !this.reviewAdmitted(turn)) return;
12391
12460
  const epoch = this.reviewEpoch;
12392
12461
  const policy = this.reviewPolicy();
@@ -12506,11 +12575,8 @@ var MnemonAgentLifecycle = class {
12506
12575
  this.reviewRunning = true;
12507
12576
  this.reviewController = controller;
12508
12577
  try {
12509
- const available = await this.coordinator.checkReviewAccess(this.agent, controller.signal);
12510
12578
  if (epoch !== this.reviewEpoch || policy !== this.reviewPolicy() || !this.idleReviewAllowed()) controller.abort(/* @__PURE__ */ new Error("Automatic idle review admission changed"));
12511
12579
  controller.signal.throwIfAborted();
12512
- this.reviewAccessBlocked = !available;
12513
- if (!available) return;
12514
12580
  if (this.config.idleReview.persistWatermark) try {
12515
12581
  if (!this.restoreWatermark() || this.watermarkStore === void 0 || !this.idleReviewAllowed()) return;
12516
12582
  const reserved = this.watermarkStore.reserve({
@@ -13315,7 +13381,21 @@ var MnemonLifecycle = class {
13315
13381
  })));
13316
13382
  stops.push(agent.ctx.on("agent/pre-step", (async (payload, next) => {
13317
13383
  const decision = await next();
13318
- if (decision.kind === "reject" || payload.signal.aborted) memory.end(payload.turn);
13384
+ if (decision.kind === "reject" || payload.signal.aborted) {
13385
+ memory.end(payload.turn);
13386
+ return decision;
13387
+ }
13388
+ if (openAgentTurn(agent) !== payload.turn) return decision;
13389
+ const recovering = memory.current?.turn !== payload.turn;
13390
+ await memory.begin(payload.turn, payload.signal);
13391
+ const pinned = memory.current;
13392
+ if (recovering && payload.step === 1 && this.config.lifecycleEnabled && pinned !== void 0) {
13393
+ const wake = modelMemoryWake(pinned.graph, pinned.context);
13394
+ if (wake.text.trim() !== "") return {
13395
+ kind: "enter",
13396
+ messages: [...decision.messages, createPluginMessage(wake.text, "recall")]
13397
+ };
13398
+ }
13319
13399
  return decision;
13320
13400
  })));
13321
13401
  stops.push(agent.ctx.on("session/event", ((session, event) => {
@@ -14136,8 +14216,6 @@ var TrajectoryWindow = class {
14136
14216
  };
14137
14217
  //#endregion
14138
14218
  //#region src/host/skill-evolution.ts
14139
- /** A new skill goes to the user's own DSH skills unless the page chose another root. */
14140
- const DEFAULT_TARGET_ROOT = "user-dsh";
14141
14219
  /** A conversation's turn bars read at most this many of its reviews. */
14142
14220
  const ACTIVITY_REVIEWS = 50;
14143
14221
  const WAITING_STATUSES = /* @__PURE__ */ new Set([
@@ -14159,6 +14237,7 @@ const BACKTEST_CONCURRENCY = 2;
14159
14237
  */
14160
14238
  function retryingSource(source, attempts = 4) {
14161
14239
  return {
14240
+ ...source.scope === void 0 ? {} : { scope: source.scope },
14162
14241
  read: (operation, input, signal) => source.read(operation, input, signal),
14163
14242
  mutate: async (operation, input, signal) => {
14164
14243
  for (let attempt = 1;; attempt++) try {
@@ -14172,6 +14251,35 @@ function retryingSource(source, attempts = 4) {
14172
14251
  function message(error) {
14173
14252
  return redact(error instanceof Error ? error.message : String(error));
14174
14253
  }
14254
+ /** Resolve only roots supplied by this Source's scoped catalog, never the task's cwd. */
14255
+ function newSkillRoot(settings, source, catalog, explicit) {
14256
+ let root = explicit;
14257
+ if (root === void 0) {
14258
+ if (settings.targetScope === "storage" && source.scope === void 0) throw new Error("The skill destination needs a known storage scope; select User or Project in Skills settings.");
14259
+ root = settings.targetScope === "project" || settings.targetScope === "storage" && (source.scope?.storage === "workspace" || source.scope?.storage === "workspaces") ? "project-dsh" : "user-dsh";
14260
+ }
14261
+ const directory = catalog.roots.find((candidate) => candidate.kind === root)?.path;
14262
+ if (directory === void 0 || root.startsWith("project-") && catalog.projectRoot === void 0) throw new Error("Project skill roots need a selected workspace; select a workspace or choose User in Skills settings.");
14263
+ if (root.startsWith("project-")) try {
14264
+ if (!statSync(catalog.projectRoot).isDirectory()) throw new Error("not a directory");
14265
+ } catch {
14266
+ throw new Error("The selected workspace project is unavailable; select an existing workspace or choose User in Skills settings.");
14267
+ }
14268
+ let parent = directory;
14269
+ try {
14270
+ for (;;) try {
14271
+ if (!statSync(parent).isDirectory()) throw new Error("not a directory");
14272
+ accessSync(parent, constants.W_OK | constants.X_OK);
14273
+ break;
14274
+ } catch (error) {
14275
+ if (error.code !== "ENOENT" || dirname(parent) === parent) throw error;
14276
+ parent = dirname(parent);
14277
+ }
14278
+ } catch {
14279
+ throw new Error(`Cannot create a skill in ${root}: its directory is not writable. Choose another destination or fix its permissions.`);
14280
+ }
14281
+ return root;
14282
+ }
14175
14283
  /**
14176
14284
  * The Host half of skill evolution: it captures each conversation's
14177
14285
  * trajectory as turns end, decides when a window has earned a review, runs
@@ -14351,7 +14459,8 @@ var SkillEvolution = class {
14351
14459
  annotations: []
14352
14460
  };
14353
14461
  try {
14354
- const active = (await source.read("catalog", null, controller.signal)).skills.filter((skill) => skill.shadowedBy === void 0);
14462
+ const catalog = await source.read("catalog", null, controller.signal);
14463
+ const active = catalog.skills.filter((skill) => skill.shadowedBy === void 0);
14355
14464
  const openSuggestions = [...await source.read("suggestions", { status: [
14356
14465
  "pending",
14357
14466
  "drafting",
@@ -14398,6 +14507,7 @@ var SkillEvolution = class {
14398
14507
  expiryDays: current.expiryDays,
14399
14508
  maxPendingPerSession: current.maxPendingPerSession
14400
14509
  };
14510
+ const targetRoot = policy.allowCreate && parsed.suggestions.some((suggestion) => suggestion.action === "create" && !active.some((skill) => skill.name === suggestion.skillName)) ? newSkillRoot(current, source, catalog) : void 0;
14401
14511
  const result = await source.mutate("review-record", {
14402
14512
  review: {
14403
14513
  ...base,
@@ -14407,7 +14517,10 @@ var SkillEvolution = class {
14407
14517
  },
14408
14518
  trajectory,
14409
14519
  annotations: parsed.annotations,
14410
- suggestions: parsed.suggestions,
14520
+ suggestions: parsed.suggestions.map((suggestion) => suggestion.action === "create" && targetRoot !== void 0 ? {
14521
+ ...suggestion,
14522
+ targetRoot
14523
+ } : suggestion),
14411
14524
  cases: parsed.cases,
14412
14525
  policy
14413
14526
  });
@@ -14454,13 +14567,27 @@ var SkillEvolution = class {
14454
14567
  */
14455
14568
  async draft(port, workspace, input) {
14456
14569
  const source = retryingSource(port);
14570
+ const pack = await source.read("evidence-pack", {
14571
+ ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId },
14572
+ ...input.skill === void 0 ? {} : { skill: input.skill }
14573
+ });
14574
+ const previous = pack.suggestion?.draftId === void 0 ? (await source.read("drafts", {
14575
+ skill: pack.skillName,
14576
+ ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId }
14577
+ })).find((draft) => draft.suggestionId === input.suggestionId) : (await source.read("draft", { id: pack.suggestion.draftId })).draft;
14578
+ const targetRoot = pack.action === "update" ? pack.skill.root : newSkillRoot(this.host.settings(), source, await source.read("catalog"), input.targetRoot ?? previous?.targetRoot ?? pack.suggestion?.targetRoot);
14579
+ const guidance = [...previous?.guidance ?? [], ...input.guidance?.trim() ? [input.guidance.trim()] : []].slice(-8);
14457
14580
  if (input.suggestionId !== void 0) await source.mutate("suggestion-status", {
14458
14581
  id: input.suggestionId,
14459
14582
  status: "drafting"
14460
14583
  });
14461
14584
  this.background("skill draft", async (signal) => {
14462
14585
  try {
14463
- await this.writeDraft(source, workspace, input, signal);
14586
+ await this.writeDraft(source, workspace, input, {
14587
+ pack,
14588
+ targetRoot,
14589
+ guidance
14590
+ }, signal);
14464
14591
  } catch (error) {
14465
14592
  if (input.suggestionId !== void 0) await source.mutate("suggestion-status", {
14466
14593
  id: input.suggestionId,
@@ -14475,14 +14602,9 @@ var SkillEvolution = class {
14475
14602
  ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId }
14476
14603
  };
14477
14604
  }
14478
- async writeDraft(source, workspace, input, signal) {
14479
- const pack = await source.read("evidence-pack", {
14480
- ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId },
14481
- ...input.skill === void 0 ? {} : { skill: input.skill }
14482
- }, signal);
14605
+ async writeDraft(source, workspace, input, prepared, signal) {
14483
14606
  const settings = this.host.settings();
14484
- const guidance = [...pack.drafts.find((draft) => input.suggestionId === void 0 ? draft.suggestionId === void 0 : draft.suggestionId === input.suggestionId)?.guidance ?? [], ...input.guidance?.trim() ? [input.guidance.trim()] : []].slice(-8);
14485
- const targetRoot = input.targetRoot ?? DEFAULT_TARGET_ROOT;
14607
+ const { pack, targetRoot, guidance } = prepared;
14486
14608
  let prompt = skillDraftPrompt(pack, guidance, targetRoot);
14487
14609
  let draft;
14488
14610
  for (let attempt = 0; draft === void 0; attempt++) {
@@ -14908,10 +15030,16 @@ async function catalog(runtime, lifecycle) {
14908
15030
  }
14909
15031
  async function compositionStatus(runtime) {
14910
15032
  const composition = runtime.graph.memoryComposition.inspect();
14911
- const strategyTypeId = runtime.graph.memoryComposition.current()?.strategy.definition.manifest.typeId;
15033
+ const strategy = runtime.graph.memoryComposition.current()?.strategy.definition.manifest;
14912
15034
  return {
14913
15035
  serving: composition.servingGenerationId !== void 0,
14914
- ...strategyTypeId === void 0 ? {} : { strategyTypeId },
15036
+ ...strategy === void 0 ? {} : {
15037
+ strategyTypeId: strategy.typeId,
15038
+ workflows: {
15039
+ idleReview: strategy.typeId === mainStrategyTypeId(runtime.graph.config) && strategyDrives(strategy, "idle-review"),
15040
+ runtimeCapacity: strategyDrives(strategy, "runtime-capacity")
15041
+ }
15042
+ },
14915
15043
  evaluation: composition.evaluation,
14916
15044
  sources: (await catalog(runtime)).sources,
14917
15045
  configuration: runtime.graph.config.memoryTopology