norn-cli 4.6.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +151 -12
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -199700,6 +199700,9 @@ var AgentInvocationRunner = class {
199700
199700
  inputTokenBudget;
199701
199701
  invocationCount = 0;
199702
199702
  toolResultStore = new AgentToolResultStore();
199703
+ rootInvocationId;
199704
+ journalEntryCount = 0;
199705
+ journalKeys = /* @__PURE__ */ new Map();
199703
199706
  async run(agent, input2) {
199704
199707
  throwIfSequenceCancelled(this.options.signal);
199705
199708
  const rootInvocationIndex2 = this.options.rootInvocationIndex ?? 1;
@@ -199725,6 +199728,7 @@ var AgentInvocationRunner = class {
199725
199728
  this.options.onInvocationActivity?.({
199726
199729
  state,
199727
199730
  invocationId: hop.invocationId,
199731
+ ...this.rootInvocationId ? { rootInvocationId: this.rootInvocationId } : {},
199728
199732
  ...hop.parentInvocationId ? { parentInvocationId: hop.parentInvocationId } : {},
199729
199733
  ...hop.hopPath ? { hopPath: hop.hopPath } : {},
199730
199734
  agent: hop.agent,
@@ -199733,6 +199737,39 @@ var AgentInvocationRunner = class {
199733
199737
  ...name ? { name } : {}
199734
199738
  });
199735
199739
  }
199740
+ reportJournalStatus(hop, input2) {
199741
+ const kind = input2.kind;
199742
+ const summary = typeof input2.summary === "string" ? input2.summary.trim() : "";
199743
+ const next = typeof input2.next === "string" ? input2.next.trim() : void 0;
199744
+ const kinds = ["new_work", "finding", "obstacle", "direction", "milestone"];
199745
+ if (!kinds.includes(kind) || !summary || summary.length > 280 || next !== void 0 && next.length > 160) {
199746
+ return { accepted: false, reason: "Use a listed kind, a 1\u2013280 character summary, and at most 160 characters for next." };
199747
+ }
199748
+ const keys = this.journalKeys.get(hop.invocationId) ?? /* @__PURE__ */ new Set();
199749
+ this.journalKeys.set(hop.invocationId, keys);
199750
+ const key = JSON.stringify([kind, summary, next ?? ""]);
199751
+ if (keys.has(key)) {
199752
+ return { accepted: true, reason: "Duplicate note omitted." };
199753
+ }
199754
+ if (keys.size >= 12 || this.journalEntryCount >= 48) {
199755
+ return { accepted: false, reason: "Journal limit reached. Continue the task without further status reports." };
199756
+ }
199757
+ keys.add(key);
199758
+ this.journalEntryCount++;
199759
+ if (this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
199760
+ this.options.onJournalEvent({
199761
+ type: "note",
199762
+ timestamp: Date.now(),
199763
+ rootInvocationId: this.rootInvocationId,
199764
+ invocationId: hop.invocationId,
199765
+ agent: hop.agent,
199766
+ kind,
199767
+ summary,
199768
+ ...next ? { next } : {}
199769
+ });
199770
+ }
199771
+ return { accepted: true };
199772
+ }
199736
199773
  validateContract(agent, kind, value) {
199737
199774
  const builtIn = agent.builtInContracts?.[kind];
199738
199775
  if (builtIn) {
@@ -199874,14 +199911,35 @@ var AgentInvocationRunner = class {
199874
199911
  };
199875
199912
  parentHop.output.toolCalls.push(record2);
199876
199913
  const startedAt = Date.now();
199914
+ const journalCallId = definition.kind === "mcp" || definition.kind === "knowledge" ? this.nextInvocationId() : void 0;
199915
+ let toolFailed = false;
199916
+ if (journalCallId && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
199917
+ this.options.onJournalEvent({
199918
+ type: "tool",
199919
+ timestamp: startedAt,
199920
+ rootInvocationId: this.rootInvocationId,
199921
+ invocationId: parentHop.invocationId,
199922
+ callId: journalCallId,
199923
+ agent: parentHop.agent,
199924
+ toolKind: definition.kind,
199925
+ name: definition.name,
199926
+ state: "started"
199927
+ });
199928
+ }
199877
199929
  const activityKind = definition.kind === "agent" ? "agent" : definition.kind === "knowledge" ? "knowledge" : "mcp";
199878
- const reportsActivity = definition.kind !== "context";
199930
+ const reportsActivity = definition.kind !== "context" && definition.kind !== "journal";
199879
199931
  if (reportsActivity) {
199880
199932
  this.emitActivity(parentHop, "active", activityKind, definition.name);
199881
199933
  }
199882
199934
  try {
199883
199935
  const output2 = await execute(input2, context);
199936
+ if (output2 && typeof output2 === "object" && "isError" in output2 && output2.isError === true) {
199937
+ toolFailed = true;
199938
+ }
199884
199939
  record2.output = cloneSnapshotValue(output2, null);
199940
+ if (definition.kind === "journal") {
199941
+ return output2;
199942
+ }
199885
199943
  if (definition.kind === "context") {
199886
199944
  const text = agentInputToText(output2);
199887
199945
  return new PreparedAgentToolResult(output2, output2, text, JSON.stringify({
@@ -199899,10 +199957,25 @@ var AgentInvocationRunner = class {
199899
199957
  ownerId: parentHop.invocationId
199900
199958
  });
199901
199959
  } catch (error2) {
199960
+ toolFailed = true;
199902
199961
  record2.output = getToolErrorOutput(error2);
199903
199962
  throw error2;
199904
199963
  } finally {
199905
199964
  record2.ms = context.recordedMs ?? Date.now() - startedAt;
199965
+ if (journalCallId && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
199966
+ this.options.onJournalEvent({
199967
+ type: "tool",
199968
+ timestamp: Date.now(),
199969
+ rootInvocationId: this.rootInvocationId,
199970
+ invocationId: parentHop.invocationId,
199971
+ callId: journalCallId,
199972
+ agent: parentHop.agent,
199973
+ toolKind: definition.kind,
199974
+ name: definition.name,
199975
+ state: "completed",
199976
+ ...toolFailed ? { failed: true } : {}
199977
+ });
199978
+ }
199906
199979
  if (context.childInvocationId) {
199907
199980
  record2.childInvocationId = context.childInvocationId;
199908
199981
  }
@@ -199942,10 +200015,13 @@ var AgentInvocationRunner = class {
199942
200015
  this.options.onInvocationProgress?.({ phase: "completed", hop });
199943
200016
  return hop;
199944
200017
  }
199945
- async invokeAgent(agent, input2, depth, parentInvocationId, ancestors, conversation, hopPath) {
200018
+ async invokeAgent(agent, input2, depth, parentInvocationId, ancestors, conversation, hopPath, parentAgentName) {
199946
200019
  throwIfSequenceCancelled(this.options.signal);
199947
200020
  const startedAt = Date.now();
199948
200021
  const invocationId = this.nextInvocationId();
200022
+ if (depth === 0) {
200023
+ this.rootInvocationId = invocationId;
200024
+ }
199949
200025
  const hop = {
199950
200026
  invocationId,
199951
200027
  ...parentInvocationId ? { parentInvocationId } : {},
@@ -199971,6 +200047,18 @@ var AgentInvocationRunner = class {
199971
200047
  depth,
199972
200048
  cycle: hop.cycle
199973
200049
  });
200050
+ if (parentInvocationId && parentAgentName && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
200051
+ this.options.onJournalEvent({
200052
+ type: "handoff",
200053
+ timestamp: Date.now(),
200054
+ rootInvocationId: this.rootInvocationId,
200055
+ invocationId,
200056
+ parentInvocationId,
200057
+ parentAgent: parentAgentName,
200058
+ agent: agent.name,
200059
+ input: input2
200060
+ });
200061
+ }
199974
200062
  this.emitActivity(hop, "active", "preparing");
199975
200063
  let resolvedModel;
199976
200064
  let fatalToolError;
@@ -200031,6 +200119,9 @@ var AgentInvocationRunner = class {
200031
200119
  invocationId
200032
200120
  );
200033
200121
  let systemPrompt = preparedKnowledge?.assembly.systemPrompt ?? authoredSystemPrompt;
200122
+ if (this.options.onJournalEvent && !requestOverride) {
200123
+ systemPrompt += "\n\nDevelopment work journal: call report_progress only when starting a distinct piece of work, finding something that changes the plan, encountering an obstacle, changing direction, or finishing a milestone. Keep each note short and factual about your own observed work. Do not report every tool call or use it as a heartbeat. Each report uses a model/tool turn and counts toward max_turns.";
200124
+ }
200034
200125
  hop.accepts = this.validateContract(agent, "accepts", input2);
200035
200126
  if (hop.accepts && !hop.accepts.assertion.passed) {
200036
200127
  throw new Error(contractFailureMessage(hop.accepts));
@@ -200205,6 +200296,16 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200205
200296
  if (reportedError instanceof AgentProviderRefusalError) {
200206
200297
  hop.output.text = reportedError.rawText;
200207
200298
  }
200299
+ if (hop.error && this.rootInvocationId && this.options.onJournalEvent) {
200300
+ this.options.onJournalEvent({
200301
+ type: "runtime",
200302
+ timestamp: Date.now(),
200303
+ rootInvocationId: this.rootInvocationId,
200304
+ invocationId,
200305
+ agent: agent.name,
200306
+ summary: `${agent.name} stopped: ${hop.error.slice(0, 280)}`
200307
+ });
200308
+ }
200208
200309
  } finally {
200209
200310
  await this.options.mcpSessionManager?.closeScope(invocationId);
200210
200311
  if (this.options.signal?.aborted) {
@@ -200255,9 +200356,13 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200255
200356
  overrideKeys.add(key);
200256
200357
  }
200257
200358
  let callerLimitBreaches = 0;
200258
- if (agent.callableAgents.some((reference) => ["load_knowledge", "norn_read_tool_result", "norn_search_tool_results"].includes(reference.name.toLowerCase()))) {
200359
+ const reservedAgentNames = ["load_knowledge", "norn_read_tool_result", "norn_search_tool_results"];
200360
+ if (this.options.onJournalEvent || requestToolsOverride?.some((tool) => tool.kind === "journal")) {
200361
+ reservedAgentNames.push("report_progress");
200362
+ }
200363
+ if (agent.callableAgents.some((reference) => reservedAgentNames.includes(reference.name.toLowerCase()))) {
200259
200364
  throw new Error(
200260
- "Agent names 'load_knowledge', 'norn_read_tool_result' and 'norn_search_tool_results' are reserved by Norn."
200365
+ `Agent names ${reservedAgentNames.map((name) => `'${name}'`).join(", ")} are reserved by Norn.`
200261
200366
  );
200262
200367
  }
200263
200368
  if (knowledge?.hasLoadableKnowledge) {
@@ -200504,7 +200609,8 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200504
200609
  parentHop.invocationId,
200505
200610
  ancestors,
200506
200611
  [...conversation],
200507
- childHopPath
200612
+ childHopPath,
200613
+ parentHop.agent
200508
200614
  );
200509
200615
  toolContext.childInvocationId = child.invocationId;
200510
200616
  for (const retry2 of pendingInputRetries) {
@@ -200563,8 +200669,28 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200563
200669
  return Object.prototype.hasOwnProperty.call(child.output, "body") ? child.output.body : child.output.text;
200564
200670
  }));
200565
200671
  }
200672
+ const offeredJournal = requestToolsOverride?.find((tool) => tool.kind === "journal" && tool.name === "report_progress");
200673
+ if (this.options.onJournalEvent || offeredJournal) {
200674
+ providerTools.push(this.createRecordedProviderTool(parentHop, {
200675
+ name: "report_progress",
200676
+ providerName: offeredJournal?.providerName ?? "norn_report_progress",
200677
+ kind: "journal",
200678
+ description: offeredJournal?.description ?? "Record a short, meaningful development work-journal note. Use sparingly for a new piece of work, finding, obstacle, change of direction, or milestone. Do not report routine tool calls. This call uses one of your allowed model/tool turns.",
200679
+ inputSchema: offeredJournal?.inputSchema ?? {
200680
+ type: "object",
200681
+ properties: {
200682
+ kind: { type: "string", enum: ["new_work", "finding", "obstacle", "direction", "milestone"] },
200683
+ summary: { type: "string", minLength: 1, maxLength: 280 },
200684
+ next: { type: "string", maxLength: 160 }
200685
+ },
200686
+ required: ["kind", "summary"],
200687
+ additionalProperties: false
200688
+ }
200689
+ }, async (input2) => this.reportJournalStatus(parentHop, input2)));
200690
+ }
200691
+ const hasRecoverableTools = providerTools.some((tool) => tool.kind !== "journal");
200566
200692
  const offeredContext = requestToolsOverride?.find((tool) => tool.name === "norn_read_tool_result" && tool.kind === "context");
200567
- if (!requestToolsOverride && providerTools.length > 0 || offeredContext) {
200693
+ if (!requestToolsOverride && hasRecoverableTools || offeredContext) {
200568
200694
  providerTools.push(this.createRecordedProviderTool(parentHop, {
200569
200695
  name: "norn_read_tool_result",
200570
200696
  providerName: offeredContext?.providerName ?? "norn_read_tool_result",
@@ -200583,7 +200709,7 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200583
200709
  }, async (input2) => this.toolResultStore.read(input2, parentHop.invocationId)));
200584
200710
  }
200585
200711
  const offeredSearch = requestToolsOverride?.find((tool) => tool.name === "norn_search_tool_results" && tool.kind === "context");
200586
- if (!requestToolsOverride && providerTools.length > 0 || offeredSearch) {
200712
+ if (!requestToolsOverride && hasRecoverableTools || offeredSearch) {
200587
200713
  providerTools.push(this.createRecordedProviderTool(parentHop, {
200588
200714
  name: "norn_search_tool_results",
200589
200715
  providerName: offeredSearch?.providerName ?? "norn_search_tool_results",
@@ -201927,7 +202053,7 @@ function isSafeProjectRelativePath(value) {
201927
202053
  return isRelativePath(value) && !normalizePathSeparators(value).split("/").includes("..");
201928
202054
  }
201929
202055
  function isAgentToolCall(value) {
201930
- return isObjectRecord2(value) && typeof value.name === "string" && (value.kind === void 0 || value.kind === "mcp" || value.kind === "agent" || value.kind === "knowledge" || value.kind === "context") && isObjectRecord2(value.input) && isFiniteNonNegativeNumber(value.ms) && (value.childInvocationId === void 0 || typeof value.childInvocationId === "string");
202056
+ return isObjectRecord2(value) && typeof value.name === "string" && (value.kind === void 0 || value.kind === "mcp" || value.kind === "agent" || value.kind === "knowledge" || value.kind === "context" || value.kind === "journal") && isObjectRecord2(value.input) && isFiniteNonNegativeNumber(value.ms) && (value.childInvocationId === void 0 || typeof value.childInvocationId === "string");
201931
202057
  }
201932
202058
  function isAgentKnowledgeTopicSnapshot(value) {
201933
202059
  return isObjectRecord2(value) && typeof value.topicHash === "string" && (value.heading === void 0 || typeof value.heading === "string") && isFiniteNonNegativeNumber(value.estimatedTokens) && isStringArray(value.itemPaths);
@@ -201943,7 +202069,7 @@ function isAgentRequestSnapshot(value) {
201943
202069
  (message) => isObjectRecord2(message) && (message.role === "user" || message.role === "assistant") && typeof message.content === "string"
201944
202070
  );
201945
202071
  const validTools = value.tools.every(
201946
- (tool) => isObjectRecord2(tool) && typeof tool.name === "string" && typeof tool.providerName === "string" && (tool.kind === void 0 || tool.kind === "mcp" || tool.kind === "agent" || tool.kind === "knowledge" || tool.kind === "context") && (tool.description === void 0 || typeof tool.description === "string") && isObjectRecord2(tool.inputSchema)
202072
+ (tool) => isObjectRecord2(tool) && typeof tool.name === "string" && typeof tool.providerName === "string" && (tool.kind === void 0 || tool.kind === "mcp" || tool.kind === "agent" || tool.kind === "knowledge" || tool.kind === "context" || tool.kind === "journal") && (tool.description === void 0 || typeof tool.description === "string") && isObjectRecord2(tool.inputSchema)
201947
202073
  );
201948
202074
  const validKnowledge = value.knowledge === void 0 || isObjectRecord2(value.knowledge) && typeof value.knowledge.runId === "string" && typeof value.knowledge.snapshotId === "string" && isStringArray(value.knowledge.declarationIds) && ["exact_entity", "lexical_unique", "lexical_ambiguous", "no_match"].includes(
201949
202075
  String(value.knowledge.routingRule)
@@ -207581,7 +207707,19 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
207581
207707
  nestingDepth,
207582
207708
  agentActivity
207583
207709
  });
207584
- }
207710
+ },
207711
+ ...executionContext?.agentWorkJournal ? {
207712
+ onJournalEvent: (agentJournal) => {
207713
+ onProgress?.({
207714
+ currentStep: stepIdx + 1,
207715
+ totalSteps,
207716
+ stepType: "agent",
207717
+ stepDescription: `${agentJournal.agent} work journal`,
207718
+ nestingDepth,
207719
+ agentJournal
207720
+ });
207721
+ }
207722
+ } : {}
207585
207723
  });
207586
207724
  const run = await runner.run(agent, input2);
207587
207725
  hop = run.hop;
@@ -208295,6 +208433,7 @@ ${indentMultiline(userMessage)}`;
208295
208433
  agentEventLog,
208296
208434
  agentRootInvocationCounts,
208297
208435
  recordAgentRuns: executionContext?.recordAgentRuns,
208436
+ agentWorkJournal: executionContext?.agentWorkJournal,
208298
208437
  agentRecordingOutputPath: executionContext?.agentRecordingOutputPath,
208299
208438
  knowledgeCoordinator,
208300
208439
  signal: executionContext?.signal
@@ -209528,7 +209667,7 @@ function formatAgentHopAtDepth(hop, options, displayDepth) {
209528
209667
  const renderedChildren = /* @__PURE__ */ new Set();
209529
209668
  const childrenByInvocationId = new Map(hop.children.map((child) => [child.invocationId, child]));
209530
209669
  for (const toolCall of hop.output.toolCalls) {
209531
- if (toolCall.kind === "context" || toolCall.kind === "knowledge" && isKnowledgeAlreadyLoaded(toolCall.output)) {
209670
+ if (toolCall.kind === "context" || toolCall.kind === "journal" || toolCall.kind === "knowledge" && isKnowledgeAlreadyLoaded(toolCall.output)) {
209532
209671
  continue;
209533
209672
  }
209534
209673
  if (toolCall.kind === "agent" || toolCall.childInvocationId) {
@@ -214683,7 +214822,7 @@ async function main() {
214683
214822
  }
214684
214823
  const args = rawArgs[0] === "run" ? rawArgs.slice(1) : rawArgs;
214685
214824
  if (args.length === 1 && (args[0] === "--version" || args[0] === "-V")) {
214686
- console.log(`Norn CLI ${"4.5.1"}`);
214825
+ console.log(`Norn CLI ${"4.6.0"}`);
214687
214826
  return;
214688
214827
  }
214689
214828
  if (args.length === 0) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn",
4
4
  "description": "Build version-controlled agentic flows in VS Code and run them from the CLI or CI.",
5
- "version": "4.6.0",
5
+ "version": "4.6.1",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"