u-foo 2.5.13 → 2.5.15

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/agents/prompts/native/environment.js +20 -8
  3. package/src/code/agent.js +339 -24
  4. package/src/code/commands.js +61 -0
  5. package/src/code/context/artifactGc.js +292 -0
  6. package/src/code/context/artifactIndex.js +161 -0
  7. package/src/code/context/artifacts.js +183 -0
  8. package/src/code/context/assembler.js +698 -0
  9. package/src/code/context/executionSegment.js +314 -0
  10. package/src/code/context/featureFlag.js +13 -0
  11. package/src/code/context/index.js +18 -0
  12. package/src/code/context/projectSnapshot.js +201 -0
  13. package/src/code/context/promptLayers.js +159 -0
  14. package/src/code/context/reducers.js +328 -0
  15. package/src/code/context/stableJson.js +29 -0
  16. package/src/code/context/stateCommit.js +412 -0
  17. package/src/code/context/transcript.js +182 -0
  18. package/src/code/context/transcriptSync.js +106 -0
  19. package/src/code/context/workingSet.js +323 -0
  20. package/src/code/dispatch.js +4 -1
  21. package/src/code/index.js +6 -0
  22. package/src/code/modelCommand.js +87 -0
  23. package/src/code/nativeRunner.js +187 -31
  24. package/src/code/repl.js +36 -32
  25. package/src/code/sessionStore.js +227 -15
  26. package/src/code/skills/index.js +10 -0
  27. package/src/code/skills/injection.js +65 -3
  28. package/src/code/skills/loader.js +21 -0
  29. package/src/code/skills/manifest.js +87 -0
  30. package/src/code/skills/render.js +15 -1
  31. package/src/code/taskDecomposer.js +32 -2
  32. package/src/code/tools/artifactRead.js +40 -0
  33. package/src/code/tui.js +2 -0
  34. package/src/code/usageStore.js +15 -0
  35. package/src/ui/format/index.js +260 -44
  36. package/src/ui/format/markdownRenderer.js +215 -72
  37. package/src/ui/ink/ChatApp.js +39 -8
  38. package/src/ui/ink/UcodeApp.js +408 -55
  39. package/src/ui/ink/chatLogModel.js +102 -21
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
 
3
+ const { renderMarkdownLinesAnsi } = require("../format/markdownRenderer");
4
+
5
+ const MARKDOWN_BODY_KINDS = new Set(["assistant", "agent", "plain", "error", "success"]);
6
+
3
7
  function stripBlessedTags(text = "") {
4
8
  return String(text || "")
5
9
  .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "")
@@ -26,6 +30,39 @@ function compactDividerLabel(text = "") {
26
30
  return label || String(text || "").trim() || "section";
27
31
  }
28
32
 
33
+ function splitSpeakerBody(raw = "") {
34
+ const source = String(raw || "");
35
+ const dotIdx = source.indexOf(" · ");
36
+ if (dotIdx >= 0) {
37
+ return {
38
+ speaker: source.slice(0, dotIdx).trim(),
39
+ body: source.slice(dotIdx + 3),
40
+ };
41
+ }
42
+ const colonMatch = source.match(/^([A-Za-z0-9_.:@/-]{1,42}):\s+(.*)$/);
43
+ if (colonMatch) {
44
+ return { speaker: colonMatch[1], body: colonMatch[2] || "" };
45
+ }
46
+ return { speaker: "", body: source };
47
+ }
48
+
49
+ function formatChatLogBody(body = "", kind = "plain", markdownState = null) {
50
+ const text = String(body || "");
51
+ if (!MARKDOWN_BODY_KINDS.has(kind)) return text;
52
+ // Structural markers / empty rows stay untouched.
53
+ if (!text.trim()) return text;
54
+ try {
55
+ const state = markdownState && typeof markdownState === "object"
56
+ ? markdownState
57
+ : { inCodeBlock: false };
58
+ const lines = renderMarkdownLinesAnsi(text, state);
59
+ if (!Array.isArray(lines) || lines.length === 0) return text;
60
+ return lines.length === 1 ? lines[0] : lines.join("\n");
61
+ } catch {
62
+ return text;
63
+ }
64
+ }
65
+
29
66
  function classifyChatLogLine(text = "") {
30
67
  const raw = stripBlessedTags(text).replace(/\r/g, "");
31
68
  const clean = stripMarkdownDecorators(raw);
@@ -41,23 +78,47 @@ function classifyChatLogLine(text = "") {
41
78
  return { kind: "meta", marker: "·", speaker: "", body: clean };
42
79
  }
43
80
  if (/^(error:|✗|failed\b)/i.test(trimmed)) {
44
- return { kind: "error", marker: "!", speaker: "error", body: clean.replace(/^(error:\s*)/i, "") };
81
+ const rawBody = raw.replace(/^(error:\s*)/i, "");
82
+ return {
83
+ kind: "error",
84
+ marker: "!",
85
+ speaker: "error",
86
+ body: rawBody || clean.replace(/^(error:\s*)/i, ""),
87
+ };
45
88
  }
46
89
  if (/^(✓|✔|done\b|closed\b)/i.test(trimmed)) {
47
- return { kind: "success", marker: "✓", speaker: "", body: clean.replace(/^[✓✔]\s*/, "") };
90
+ const rawBody = raw.replace(/^[✓✔]\s*/, "");
91
+ return {
92
+ kind: "success",
93
+ marker: "✓",
94
+ speaker: "",
95
+ body: rawBody || clean.replace(/^[✓✔]\s*/, ""),
96
+ };
48
97
  }
49
- const dotMatch = clean.match(/^([^·\n]{1,64})\s+·\s+(.*)$/);
50
- if (dotMatch) {
51
- const speaker = dotMatch[1].trim();
98
+ const cleanDot = clean.match(/^([^·\n]{1,64})\s+·\s+(.*)$/);
99
+ if (cleanDot) {
100
+ const parts = splitSpeakerBody(raw);
101
+ const speaker = stripMarkdownDecorators(parts.speaker || cleanDot[1]).trim();
52
102
  const lower = speaker.toLowerCase();
53
103
  const kind = lower === "ufoo" ? "assistant" : "agent";
54
- return { kind, marker: kind === "assistant" ? "◆" : "•", speaker, body: dotMatch[2] || " " };
104
+ return {
105
+ kind,
106
+ marker: kind === "assistant" ? "◆" : "•",
107
+ speaker,
108
+ body: parts.body != null ? parts.body : (cleanDot[2] || " "),
109
+ };
55
110
  }
56
- const colonMatch = clean.match(/^([A-Za-z0-9_.:@/-]{1,42}):\s+(.*)$/);
57
- if (colonMatch) {
58
- return { kind: "agent", marker: "•", speaker: colonMatch[1], body: colonMatch[2] || " " };
111
+ const cleanColon = clean.match(/^([A-Za-z0-9_.:@/-]{1,42}):\s+(.*)$/);
112
+ if (cleanColon) {
113
+ const parts = splitSpeakerBody(raw);
114
+ return {
115
+ kind: "agent",
116
+ marker: "•",
117
+ speaker: stripMarkdownDecorators(parts.speaker || cleanColon[1]).trim(),
118
+ body: parts.body != null ? parts.body : (cleanColon[2] || " "),
119
+ };
59
120
  }
60
- return { kind: "plain", marker: "", speaker: "", body: clean };
121
+ return { kind: "plain", marker: "", speaker: "", body: raw || clean };
61
122
  }
62
123
 
63
124
  function defaultMarkerForKind(kind = "", speaker = "") {
@@ -71,12 +132,19 @@ function defaultMarkerForKind(kind = "", speaker = "") {
71
132
  return speaker ? "•" : "";
72
133
  }
73
134
 
74
- function buildChatLogLineModel(input = "") {
135
+ function buildChatLogLineModel(input = "", options = {}) {
136
+ const markdownState = options && options.markdownState;
137
+
75
138
  if (input && typeof input === "object" && !input.kind) {
76
- return buildChatLogLineModel(chatLogEntryText(input));
139
+ return buildChatLogLineModel(chatLogEntryText(input), options);
77
140
  }
78
141
 
79
142
  if (input && typeof input === "object" && input.kind) {
143
+ // Prefer original text when present so markdown can be (re)applied with
144
+ // a shared fence state — e.g. Static decoration.
145
+ if (input.text != null && String(input.text).length > 0 && markdownState) {
146
+ return buildChatLogLineModel(String(input.text), options);
147
+ }
80
148
  const kind = String(input.kind || "plain");
81
149
  const speaker = String(input.speaker || "");
82
150
  const marker = input.marker != null ? String(input.marker) : defaultMarkerForKind(kind, speaker);
@@ -84,6 +152,9 @@ function buildChatLogLineModel(input = "") {
84
152
  ? String(input.bodyText)
85
153
  : String(input.body != null ? input.body : chatLogEntryText(input));
86
154
  const body = kind === "plain" ? compactContinuationIndent(rawBody || " ") : (rawBody || " ");
155
+ const bodyText = markdownState
156
+ ? formatChatLogBody(body, kind, markdownState)
157
+ : body;
87
158
  return {
88
159
  kind,
89
160
  marker,
@@ -92,7 +163,7 @@ function buildChatLogLineModel(input = "") {
92
163
  markerText: input.markerText != null
93
164
  ? String(input.markerText)
94
165
  : (speaker ? `${marker || " "} ` : `${marker || " "} `),
95
- bodyText: body,
166
+ bodyText,
96
167
  };
97
168
  }
98
169
 
@@ -104,7 +175,7 @@ function buildChatLogLineModel(input = "") {
104
175
  return {
105
176
  ...row,
106
177
  markerText: hasSpeaker ? `${row.marker || " "} ` : `${row.marker || " "} `,
107
- bodyText: body,
178
+ bodyText: formatChatLogBody(body, row.kind, markdownState || { inCodeBlock: false }),
108
179
  };
109
180
  }
110
181
 
@@ -113,12 +184,15 @@ function normalizeEntryInput(input) {
113
184
  return { text: input };
114
185
  }
115
186
 
116
- function createChatLogEntry(input = "", id = "") {
187
+ function createChatLogEntry(input = "", id = "", options = {}) {
117
188
  const source = normalizeEntryInput(input);
118
189
  const text = String(source.text != null ? source.text : chatLogEntryText(source));
119
- const row = source.kind
120
- ? buildChatLogLineModel({ ...source, text })
121
- : buildChatLogLineModel(text);
190
+ const markdownState = options && options.markdownState
191
+ ? options.markdownState
192
+ : { inCodeBlock: false };
193
+ const row = source.kind && !(options && options.markdownState)
194
+ ? buildChatLogLineModel({ ...source, text }, { markdownState })
195
+ : buildChatLogLineModel(text, { markdownState });
122
196
  const meta = source.meta && typeof source.meta === "object" && !Array.isArray(source.meta)
123
197
  ? { ...source.meta }
124
198
  : {};
@@ -160,13 +234,17 @@ function buildChatLogGroups(items = []) {
160
234
  const source = Array.isArray(items) ? items : [];
161
235
  const groups = [];
162
236
  let current = null;
237
+ const markdownState = { inCodeBlock: false };
163
238
  for (let index = 0; index < source.length; index += 1) {
164
239
  const item = source[index] || {};
165
240
  const itemId = item && typeof item === "object" && item.id ? item.id : `log-${index}`;
166
- const row = buildChatLogLineModel(item);
241
+ const text = item && typeof item === "object" && item.text != null
242
+ ? String(item.text)
243
+ : chatLogEntryText(item);
244
+ const row = buildChatLogLineModel(text, { markdownState });
167
245
  const entry = {
168
246
  id: itemId,
169
- text: chatLogEntryText(item),
247
+ text,
170
248
  row,
171
249
  sourceType: item && typeof item === "object" ? String(item.sourceType || item.type || "") : "",
172
250
  meta: item && typeof item === "object" && item.meta ? item.meta : {},
@@ -196,7 +274,10 @@ module.exports = {
196
274
  compactDividerLabel,
197
275
  classifyChatLogLine,
198
276
  buildChatLogLineModel,
199
- buildChatLogGroups,
277
+ formatChatLogBody,
200
278
  createChatLogEntry,
201
279
  chatLogEntryText,
280
+ canAppendToChatLogGroup,
281
+ buildChatLogGroups,
282
+ MARKDOWN_BODY_KINDS,
202
283
  };