norn-cli 4.6.0 → 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +251 -22
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -166647,6 +166647,9 @@ function parseDurationToMs(rawValue, defaultUnit = "ms") {
166647
166647
  }
166648
166648
 
166649
166649
  // src/formUrlEncoded.ts
166650
+ function decodeFieldValue(value) {
166651
+ return isQuotedStringLiteral(value) ? decodeQuotedStringLiteral(value) : value;
166652
+ }
166650
166653
  function parseEqualsField(segment) {
166651
166654
  const eqIndex = segment.indexOf("=");
166652
166655
  if (eqIndex <= 0) {
@@ -166654,9 +166657,53 @@ function parseEqualsField(segment) {
166654
166657
  }
166655
166658
  return {
166656
166659
  key: segment.substring(0, eqIndex).trim(),
166657
- value: segment.substring(eqIndex + 1).trim()
166660
+ value: decodeFieldValue(segment.substring(eqIndex + 1).trim())
166658
166661
  };
166659
166662
  }
166663
+ function splitAmpersandDelimitedLine(line2) {
166664
+ const segments = [];
166665
+ let current = "";
166666
+ let quote;
166667
+ let escaped = false;
166668
+ let inValue = false;
166669
+ let valueStarted = false;
166670
+ for (const char of line2) {
166671
+ if (escaped) {
166672
+ current += char;
166673
+ escaped = false;
166674
+ continue;
166675
+ }
166676
+ if (quote && char === "\\") {
166677
+ current += char;
166678
+ escaped = true;
166679
+ continue;
166680
+ }
166681
+ if ((char === '"' || char === "'") && (quote || inValue && !valueStarted)) {
166682
+ if (!quote) {
166683
+ quote = char;
166684
+ } else if (quote === char) {
166685
+ quote = void 0;
166686
+ }
166687
+ current += char;
166688
+ continue;
166689
+ }
166690
+ if (char === "&" && !quote) {
166691
+ segments.push(current.trim());
166692
+ current = "";
166693
+ inValue = false;
166694
+ valueStarted = false;
166695
+ continue;
166696
+ }
166697
+ current += char;
166698
+ if (!inValue && char === "=") {
166699
+ inValue = true;
166700
+ } else if (inValue && !/\s/.test(char)) {
166701
+ valueStarted = true;
166702
+ }
166703
+ }
166704
+ segments.push(current.trim());
166705
+ return segments;
166706
+ }
166660
166707
  function isFormUrlEncodedContentType(contentType) {
166661
166708
  return Boolean(contentType && contentType.toLowerCase().includes("application/x-www-form-urlencoded"));
166662
166709
  }
@@ -166672,7 +166719,7 @@ function parseFormUrlEncodedLines(lines) {
166672
166719
  const firstColonIndex = line2.indexOf(":");
166673
166720
  if (firstEqIndex > 0 && (firstColonIndex === -1 || firstEqIndex < firstColonIndex)) {
166674
166721
  if (line2.includes("&")) {
166675
- const segments = line2.split("&").map((segment) => segment.trim());
166722
+ const segments = splitAmpersandDelimitedLine(line2);
166676
166723
  const hasInvalidSegment = segments.some((segment) => !parseEqualsField(segment));
166677
166724
  if (hasInvalidSegment) {
166678
166725
  errors.push({
@@ -166699,7 +166746,7 @@ function parseFormUrlEncodedLines(lines) {
166699
166746
  if (firstColonIndex > 0) {
166700
166747
  fields.push({
166701
166748
  key: line2.substring(0, firstColonIndex).trim(),
166702
- value: line2.substring(firstColonIndex + 1).trim()
166749
+ value: decodeFieldValue(line2.substring(firstColonIndex + 1).trim())
166703
166750
  });
166704
166751
  continue;
166705
166752
  }
@@ -166723,10 +166770,13 @@ function encodeFormUrlEncodedBody(body) {
166723
166770
  return { encodedBody: "", errors };
166724
166771
  }
166725
166772
  return {
166726
- encodedBody: fields.map((field) => `${encodeURIComponent(field.key)}=${encodeURIComponent(field.value)}`).join("&"),
166773
+ encodedBody: encodeFormUrlEncodedFields(fields),
166727
166774
  errors: []
166728
166775
  };
166729
166776
  }
166777
+ function encodeFormUrlEncodedFields(fields) {
166778
+ return fields.map((field) => `${encodeURIComponent(field.key)}=${encodeURIComponent(field.value)}`).join("&");
166779
+ }
166730
166780
 
166731
166781
  // src/sequenceCancellation.ts
166732
166782
  var SEQUENCE_CANCELLED_MESSAGE = "Sequence run cancelled by user.";
@@ -166861,7 +166911,10 @@ async function sendRequestWithJar(request, jar, retryOptions, signal) {
166861
166911
  );
166862
166912
  const contentTypeValue = contentType ? headers[contentType] : "";
166863
166913
  if (isFormUrlEncodedContentType(contentTypeValue)) {
166864
- const encoded = encodeFormUrlEncodedBody(currentBody);
166914
+ const encoded = request.formUrlEncodedBody ? {
166915
+ encodedBody: encodeFormUrlEncodedFields(request.formUrlEncodedBody.fields),
166916
+ errors: request.formUrlEncodedBody.errors
166917
+ } : encodeFormUrlEncodedBody(currentBody);
166865
166918
  if (encoded.errors.length > 0) {
166866
166919
  throw new NornError({
166867
166920
  category: "syntax",
@@ -168154,7 +168207,7 @@ function validatePreparedRequest(parsed, context) {
168154
168207
  }
168155
168208
  const contentType = getHeaderValueCaseInsensitive(parsed.headers, "Content-Type");
168156
168209
  if (isFormUrlEncodedContentType(contentType)) {
168157
- const { errors } = parseFormUrlEncodedBody(parsed.body);
168210
+ const errors = parsed.formUrlEncodedBody?.errors ?? parseFormUrlEncodedBody(parsed.body).errors;
168158
168211
  if (errors.length > 0) {
168159
168212
  throw new NornError({
168160
168213
  category: "syntax",
@@ -185378,6 +185431,25 @@ var McpSessionManager = class {
185378
185431
  };
185379
185432
 
185380
185433
  // src/requestPreparation.ts
185434
+ function getHeaderValueCaseInsensitive2(headers, name) {
185435
+ const target = name.toLowerCase();
185436
+ return Object.entries(headers).find(([headerName]) => headerName.toLowerCase() === target)?.[1];
185437
+ }
185438
+ function attachPreparedFormBody(request, authoredBody, variables) {
185439
+ const contentType = getHeaderValueCaseInsensitive2(request.headers, "Content-Type");
185440
+ if (!isFormUrlEncodedContentType(contentType) || !authoredBody) {
185441
+ return request;
185442
+ }
185443
+ const parsed = parseFormUrlEncodedBody(authoredBody);
185444
+ const prepared = {
185445
+ errors: parsed.errors,
185446
+ fields: parsed.fields.map((field) => ({
185447
+ key: substituteVariables(field.key, variables),
185448
+ value: substituteVariables(field.value, variables)
185449
+ }))
185450
+ };
185451
+ return { ...request, formUrlEncodedBody: prepared };
185452
+ }
185381
185453
  function escapeRegExp(value) {
185382
185454
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
185383
185455
  }
@@ -185421,7 +185493,8 @@ function resolveBareVariableRequestBody(request, variables) {
185421
185493
  }
185422
185494
  return {
185423
185495
  ...request,
185424
- body: stringifyRequestValue(variables[bareVariableMatch[1]])
185496
+ body: stringifyRequestValue(variables[bareVariableMatch[1]]),
185497
+ formUrlEncodedBody: void 0
185425
185498
  };
185426
185499
  }
185427
185500
  function applyHeaderGroupsToRequest(parsed, requestText, headerGroups, variables) {
@@ -185530,12 +185603,13 @@ function prepareRequestFromContent(requestContent, variables, apiDefinitions) {
185530
185603
  for (const [headerName, headerValue] of Object.entries(apiRequest.inlineHeaders)) {
185531
185604
  combinedHeaders[headerName] = substituteVariables(headerValue, variables);
185532
185605
  }
185533
- const request2 = {
185606
+ let request2 = {
185534
185607
  method: apiRequest.method,
185535
185608
  url: resolvedPath,
185536
185609
  headers: combinedHeaders,
185537
185610
  body: apiRequest.body ? substituteVariables(apiRequest.body, variables) : void 0
185538
185611
  };
185612
+ request2 = attachPreparedFormBody(request2, apiRequest.body, variables);
185539
185613
  return {
185540
185614
  request: request2,
185541
185615
  description: `${apiRequest.endpointName}(${Object.values(resolvedParams).join(", ")}) -> ${request2.method} ${request2.url}`,
@@ -185544,9 +185618,12 @@ function prepareRequestFromContent(requestContent, variables, apiDefinitions) {
185544
185618
  }
185545
185619
  }
185546
185620
  let request = parserHttpRequest(requestContent, variables);
185621
+ let authoredRequest = parserHttpRequest(requestContent, {});
185547
185622
  if (apiDefinitions && apiDefinitions.headerGroups.length > 0) {
185548
185623
  request = applyHeaderGroupsToRequest(request, requestContent, apiDefinitions.headerGroups, variables);
185624
+ authoredRequest = applyHeaderGroupsToRequest(authoredRequest, requestContent, apiDefinitions.headerGroups, {});
185549
185625
  }
185626
+ request = attachPreparedFormBody(request, authoredRequest.body, variables);
185550
185627
  return {
185551
185628
  request,
185552
185629
  description: `${request.method} ${request.url}`
@@ -199700,6 +199777,9 @@ var AgentInvocationRunner = class {
199700
199777
  inputTokenBudget;
199701
199778
  invocationCount = 0;
199702
199779
  toolResultStore = new AgentToolResultStore();
199780
+ rootInvocationId;
199781
+ journalEntryCount = 0;
199782
+ journalKeys = /* @__PURE__ */ new Map();
199703
199783
  async run(agent, input2) {
199704
199784
  throwIfSequenceCancelled(this.options.signal);
199705
199785
  const rootInvocationIndex2 = this.options.rootInvocationIndex ?? 1;
@@ -199725,6 +199805,7 @@ var AgentInvocationRunner = class {
199725
199805
  this.options.onInvocationActivity?.({
199726
199806
  state,
199727
199807
  invocationId: hop.invocationId,
199808
+ ...this.rootInvocationId ? { rootInvocationId: this.rootInvocationId } : {},
199728
199809
  ...hop.parentInvocationId ? { parentInvocationId: hop.parentInvocationId } : {},
199729
199810
  ...hop.hopPath ? { hopPath: hop.hopPath } : {},
199730
199811
  agent: hop.agent,
@@ -199733,6 +199814,39 @@ var AgentInvocationRunner = class {
199733
199814
  ...name ? { name } : {}
199734
199815
  });
199735
199816
  }
199817
+ reportJournalStatus(hop, input2) {
199818
+ const kind = input2.kind;
199819
+ const summary = typeof input2.summary === "string" ? input2.summary.trim() : "";
199820
+ const next = typeof input2.next === "string" ? input2.next.trim() : void 0;
199821
+ const kinds = ["new_work", "finding", "obstacle", "direction", "milestone"];
199822
+ if (!kinds.includes(kind) || !summary || summary.length > 280 || next !== void 0 && next.length > 160) {
199823
+ return { accepted: false, reason: "Use a listed kind, a 1\u2013280 character summary, and at most 160 characters for next." };
199824
+ }
199825
+ const keys = this.journalKeys.get(hop.invocationId) ?? /* @__PURE__ */ new Set();
199826
+ this.journalKeys.set(hop.invocationId, keys);
199827
+ const key = JSON.stringify([kind, summary, next ?? ""]);
199828
+ if (keys.has(key)) {
199829
+ return { accepted: true, reason: "Duplicate note omitted." };
199830
+ }
199831
+ if (keys.size >= 12 || this.journalEntryCount >= 48) {
199832
+ return { accepted: false, reason: "Journal limit reached. Continue the task without further status reports." };
199833
+ }
199834
+ keys.add(key);
199835
+ this.journalEntryCount++;
199836
+ if (this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
199837
+ this.options.onJournalEvent({
199838
+ type: "note",
199839
+ timestamp: Date.now(),
199840
+ rootInvocationId: this.rootInvocationId,
199841
+ invocationId: hop.invocationId,
199842
+ agent: hop.agent,
199843
+ kind,
199844
+ summary,
199845
+ ...next ? { next } : {}
199846
+ });
199847
+ }
199848
+ return { accepted: true };
199849
+ }
199736
199850
  validateContract(agent, kind, value) {
199737
199851
  const builtIn = agent.builtInContracts?.[kind];
199738
199852
  if (builtIn) {
@@ -199874,14 +199988,35 @@ var AgentInvocationRunner = class {
199874
199988
  };
199875
199989
  parentHop.output.toolCalls.push(record2);
199876
199990
  const startedAt = Date.now();
199991
+ const journalCallId = definition.kind === "mcp" || definition.kind === "knowledge" ? this.nextInvocationId() : void 0;
199992
+ let toolFailed = false;
199993
+ if (journalCallId && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
199994
+ this.options.onJournalEvent({
199995
+ type: "tool",
199996
+ timestamp: startedAt,
199997
+ rootInvocationId: this.rootInvocationId,
199998
+ invocationId: parentHop.invocationId,
199999
+ callId: journalCallId,
200000
+ agent: parentHop.agent,
200001
+ toolKind: definition.kind,
200002
+ name: definition.name,
200003
+ state: "started"
200004
+ });
200005
+ }
199877
200006
  const activityKind = definition.kind === "agent" ? "agent" : definition.kind === "knowledge" ? "knowledge" : "mcp";
199878
- const reportsActivity = definition.kind !== "context";
200007
+ const reportsActivity = definition.kind !== "context" && definition.kind !== "journal";
199879
200008
  if (reportsActivity) {
199880
200009
  this.emitActivity(parentHop, "active", activityKind, definition.name);
199881
200010
  }
199882
200011
  try {
199883
200012
  const output2 = await execute(input2, context);
200013
+ if (output2 && typeof output2 === "object" && "isError" in output2 && output2.isError === true) {
200014
+ toolFailed = true;
200015
+ }
199884
200016
  record2.output = cloneSnapshotValue(output2, null);
200017
+ if (definition.kind === "journal") {
200018
+ return output2;
200019
+ }
199885
200020
  if (definition.kind === "context") {
199886
200021
  const text = agentInputToText(output2);
199887
200022
  return new PreparedAgentToolResult(output2, output2, text, JSON.stringify({
@@ -199899,10 +200034,25 @@ var AgentInvocationRunner = class {
199899
200034
  ownerId: parentHop.invocationId
199900
200035
  });
199901
200036
  } catch (error2) {
200037
+ toolFailed = true;
199902
200038
  record2.output = getToolErrorOutput(error2);
199903
200039
  throw error2;
199904
200040
  } finally {
199905
200041
  record2.ms = context.recordedMs ?? Date.now() - startedAt;
200042
+ if (journalCallId && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
200043
+ this.options.onJournalEvent({
200044
+ type: "tool",
200045
+ timestamp: Date.now(),
200046
+ rootInvocationId: this.rootInvocationId,
200047
+ invocationId: parentHop.invocationId,
200048
+ callId: journalCallId,
200049
+ agent: parentHop.agent,
200050
+ toolKind: definition.kind,
200051
+ name: definition.name,
200052
+ state: "completed",
200053
+ ...toolFailed ? { failed: true } : {}
200054
+ });
200055
+ }
199906
200056
  if (context.childInvocationId) {
199907
200057
  record2.childInvocationId = context.childInvocationId;
199908
200058
  }
@@ -199942,10 +200092,13 @@ var AgentInvocationRunner = class {
199942
200092
  this.options.onInvocationProgress?.({ phase: "completed", hop });
199943
200093
  return hop;
199944
200094
  }
199945
- async invokeAgent(agent, input2, depth, parentInvocationId, ancestors, conversation, hopPath) {
200095
+ async invokeAgent(agent, input2, depth, parentInvocationId, ancestors, conversation, hopPath, parentAgentName) {
199946
200096
  throwIfSequenceCancelled(this.options.signal);
199947
200097
  const startedAt = Date.now();
199948
200098
  const invocationId = this.nextInvocationId();
200099
+ if (depth === 0) {
200100
+ this.rootInvocationId = invocationId;
200101
+ }
199949
200102
  const hop = {
199950
200103
  invocationId,
199951
200104
  ...parentInvocationId ? { parentInvocationId } : {},
@@ -199971,6 +200124,18 @@ var AgentInvocationRunner = class {
199971
200124
  depth,
199972
200125
  cycle: hop.cycle
199973
200126
  });
200127
+ if (parentInvocationId && parentAgentName && this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
200128
+ this.options.onJournalEvent({
200129
+ type: "handoff",
200130
+ timestamp: Date.now(),
200131
+ rootInvocationId: this.rootInvocationId,
200132
+ invocationId,
200133
+ parentInvocationId,
200134
+ parentAgent: parentAgentName,
200135
+ agent: agent.name,
200136
+ input: input2
200137
+ });
200138
+ }
199974
200139
  this.emitActivity(hop, "active", "preparing");
199975
200140
  let resolvedModel;
199976
200141
  let fatalToolError;
@@ -200031,6 +200196,9 @@ var AgentInvocationRunner = class {
200031
200196
  invocationId
200032
200197
  );
200033
200198
  let systemPrompt = preparedKnowledge?.assembly.systemPrompt ?? authoredSystemPrompt;
200199
+ if (this.options.onJournalEvent && !requestOverride) {
200200
+ 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.";
200201
+ }
200034
200202
  hop.accepts = this.validateContract(agent, "accepts", input2);
200035
200203
  if (hop.accepts && !hop.accepts.assertion.passed) {
200036
200204
  throw new Error(contractFailureMessage(hop.accepts));
@@ -200205,6 +200373,16 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200205
200373
  if (reportedError instanceof AgentProviderRefusalError) {
200206
200374
  hop.output.text = reportedError.rawText;
200207
200375
  }
200376
+ if (hop.error && this.rootInvocationId && this.options.onJournalEvent) {
200377
+ this.options.onJournalEvent({
200378
+ type: "runtime",
200379
+ timestamp: Date.now(),
200380
+ rootInvocationId: this.rootInvocationId,
200381
+ invocationId,
200382
+ agent: agent.name,
200383
+ summary: `${agent.name} stopped: ${hop.error.slice(0, 280)}`
200384
+ });
200385
+ }
200208
200386
  } finally {
200209
200387
  await this.options.mcpSessionManager?.closeScope(invocationId);
200210
200388
  if (this.options.signal?.aborted) {
@@ -200255,9 +200433,13 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200255
200433
  overrideKeys.add(key);
200256
200434
  }
200257
200435
  let callerLimitBreaches = 0;
200258
- if (agent.callableAgents.some((reference) => ["load_knowledge", "norn_read_tool_result", "norn_search_tool_results"].includes(reference.name.toLowerCase()))) {
200436
+ const reservedAgentNames = ["load_knowledge", "norn_read_tool_result", "norn_search_tool_results"];
200437
+ if (this.options.onJournalEvent || requestToolsOverride?.some((tool) => tool.kind === "journal")) {
200438
+ reservedAgentNames.push("report_progress");
200439
+ }
200440
+ if (agent.callableAgents.some((reference) => reservedAgentNames.includes(reference.name.toLowerCase()))) {
200259
200441
  throw new Error(
200260
- "Agent names 'load_knowledge', 'norn_read_tool_result' and 'norn_search_tool_results' are reserved by Norn."
200442
+ `Agent names ${reservedAgentNames.map((name) => `'${name}'`).join(", ")} are reserved by Norn.`
200261
200443
  );
200262
200444
  }
200263
200445
  if (knowledge?.hasLoadableKnowledge) {
@@ -200504,7 +200686,8 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200504
200686
  parentHop.invocationId,
200505
200687
  ancestors,
200506
200688
  [...conversation],
200507
- childHopPath
200689
+ childHopPath,
200690
+ parentHop.agent
200508
200691
  );
200509
200692
  toolContext.childInvocationId = child.invocationId;
200510
200693
  for (const retry2 of pendingInputRetries) {
@@ -200560,11 +200743,44 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200560
200743
  retry2.correctedByInvocationId = child.invocationId;
200561
200744
  }
200562
200745
  outputContractFailures = 0;
200563
- return Object.prototype.hasOwnProperty.call(child.output, "body") ? child.output.body : child.output.text;
200746
+ const returnedOutput = Object.prototype.hasOwnProperty.call(child.output, "body") ? child.output.body : child.output.text;
200747
+ if (this.rootInvocationId && this.options.onJournalEvent && !this.options.signal?.aborted) {
200748
+ this.options.onJournalEvent({
200749
+ type: "return",
200750
+ timestamp: Date.now(),
200751
+ rootInvocationId: this.rootInvocationId,
200752
+ invocationId: child.invocationId,
200753
+ parentInvocationId: parentHop.invocationId,
200754
+ agent: child.agent,
200755
+ parentAgent: parentHop.agent,
200756
+ output: returnedOutput
200757
+ });
200758
+ }
200759
+ return returnedOutput;
200564
200760
  }));
200565
200761
  }
200762
+ const offeredJournal = requestToolsOverride?.find((tool) => tool.kind === "journal" && tool.name === "report_progress");
200763
+ if (this.options.onJournalEvent || offeredJournal) {
200764
+ providerTools.push(this.createRecordedProviderTool(parentHop, {
200765
+ name: "report_progress",
200766
+ providerName: offeredJournal?.providerName ?? "norn_report_progress",
200767
+ kind: "journal",
200768
+ 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.",
200769
+ inputSchema: offeredJournal?.inputSchema ?? {
200770
+ type: "object",
200771
+ properties: {
200772
+ kind: { type: "string", enum: ["new_work", "finding", "obstacle", "direction", "milestone"] },
200773
+ summary: { type: "string", minLength: 1, maxLength: 280 },
200774
+ next: { type: "string", maxLength: 160 }
200775
+ },
200776
+ required: ["kind", "summary"],
200777
+ additionalProperties: false
200778
+ }
200779
+ }, async (input2) => this.reportJournalStatus(parentHop, input2)));
200780
+ }
200781
+ const hasRecoverableTools = providerTools.some((tool) => tool.kind !== "journal");
200566
200782
  const offeredContext = requestToolsOverride?.find((tool) => tool.name === "norn_read_tool_result" && tool.kind === "context");
200567
- if (!requestToolsOverride && providerTools.length > 0 || offeredContext) {
200783
+ if (!requestToolsOverride && hasRecoverableTools || offeredContext) {
200568
200784
  providerTools.push(this.createRecordedProviderTool(parentHop, {
200569
200785
  name: "norn_read_tool_result",
200570
200786
  providerName: offeredContext?.providerName ?? "norn_read_tool_result",
@@ -200583,7 +200799,7 @@ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200583
200799
  }, async (input2) => this.toolResultStore.read(input2, parentHop.invocationId)));
200584
200800
  }
200585
200801
  const offeredSearch = requestToolsOverride?.find((tool) => tool.name === "norn_search_tool_results" && tool.kind === "context");
200586
- if (!requestToolsOverride && providerTools.length > 0 || offeredSearch) {
200802
+ if (!requestToolsOverride && hasRecoverableTools || offeredSearch) {
200587
200803
  providerTools.push(this.createRecordedProviderTool(parentHop, {
200588
200804
  name: "norn_search_tool_results",
200589
200805
  providerName: offeredSearch?.providerName ?? "norn_search_tool_results",
@@ -201927,7 +202143,7 @@ function isSafeProjectRelativePath(value) {
201927
202143
  return isRelativePath(value) && !normalizePathSeparators(value).split("/").includes("..");
201928
202144
  }
201929
202145
  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");
202146
+ 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
202147
  }
201932
202148
  function isAgentKnowledgeTopicSnapshot(value) {
201933
202149
  return isObjectRecord2(value) && typeof value.topicHash === "string" && (value.heading === void 0 || typeof value.heading === "string") && isFiniteNonNegativeNumber(value.estimatedTokens) && isStringArray(value.itemPaths);
@@ -201943,7 +202159,7 @@ function isAgentRequestSnapshot(value) {
201943
202159
  (message) => isObjectRecord2(message) && (message.role === "user" || message.role === "assistant") && typeof message.content === "string"
201944
202160
  );
201945
202161
  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)
202162
+ (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
202163
  );
201948
202164
  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
202165
  String(value.knowledge.routingRule)
@@ -207581,7 +207797,19 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
207581
207797
  nestingDepth,
207582
207798
  agentActivity
207583
207799
  });
207584
- }
207800
+ },
207801
+ ...executionContext?.agentWorkJournal ? {
207802
+ onJournalEvent: (agentJournal) => {
207803
+ onProgress?.({
207804
+ currentStep: stepIdx + 1,
207805
+ totalSteps,
207806
+ stepType: "agent",
207807
+ stepDescription: `${agentJournal.agent} work journal`,
207808
+ nestingDepth,
207809
+ agentJournal
207810
+ });
207811
+ }
207812
+ } : {}
207585
207813
  });
207586
207814
  const run = await runner.run(agent, input2);
207587
207815
  hop = run.hop;
@@ -208052,7 +208280,7 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
208052
208280
  let requestText = `${parsed.method} ${parsed.url}`;
208053
208281
  const contentLines = step.content.split("\n");
208054
208282
  if (contentLines.length > 1) {
208055
- const extraLines = contentLines.slice(1).map((line2) => substituteVariables(line2, runtimeVariables));
208283
+ const extraLines = contentLines.slice(1);
208056
208284
  const hasExplicitBlank = extraLines.some((line2) => line2.trim() === "");
208057
208285
  const firstNonEmpty = extraLines.find((line2) => line2.trim() !== "");
208058
208286
  const startsWithHeader = firstNonEmpty ? /^[A-Za-z0-9\-_]+\s*:/.test(firstNonEmpty.trim()) : false;
@@ -208295,6 +208523,7 @@ ${indentMultiline(userMessage)}`;
208295
208523
  agentEventLog,
208296
208524
  agentRootInvocationCounts,
208297
208525
  recordAgentRuns: executionContext?.recordAgentRuns,
208526
+ agentWorkJournal: executionContext?.agentWorkJournal,
208298
208527
  agentRecordingOutputPath: executionContext?.agentRecordingOutputPath,
208299
208528
  knowledgeCoordinator,
208300
208529
  signal: executionContext?.signal
@@ -209528,7 +209757,7 @@ function formatAgentHopAtDepth(hop, options, displayDepth) {
209528
209757
  const renderedChildren = /* @__PURE__ */ new Set();
209529
209758
  const childrenByInvocationId = new Map(hop.children.map((child) => [child.invocationId, child]));
209530
209759
  for (const toolCall of hop.output.toolCalls) {
209531
- if (toolCall.kind === "context" || toolCall.kind === "knowledge" && isKnowledgeAlreadyLoaded(toolCall.output)) {
209760
+ if (toolCall.kind === "context" || toolCall.kind === "journal" || toolCall.kind === "knowledge" && isKnowledgeAlreadyLoaded(toolCall.output)) {
209532
209761
  continue;
209533
209762
  }
209534
209763
  if (toolCall.kind === "agent" || toolCall.childInvocationId) {
@@ -214683,7 +214912,7 @@ async function main() {
214683
214912
  }
214684
214913
  const args = rawArgs[0] === "run" ? rawArgs.slice(1) : rawArgs;
214685
214914
  if (args.length === 1 && (args[0] === "--version" || args[0] === "-V")) {
214686
- console.log(`Norn CLI ${"4.5.1"}`);
214915
+ console.log(`Norn CLI ${"4.6.1"}`);
214687
214916
  return;
214688
214917
  }
214689
214918
  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.2",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"