thincoder 0.12.26 → 0.12.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.26",
3
+ "version": "0.12.28",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * agent/setup.mjs — runAgent pre-flight setup: context injection, system prompt construction, tool injection
3
3
  */
4
+ /** Local time, second precision, for the per-run transient reminder. */
5
+ function timeNowLocal() {
6
+ return new Date().toLocaleString("sv-SE")
7
+ }
8
+
4
9
  import { search as memorySearch, docSearch } from "../memory.mjs"
5
10
  import { pushReal } from "../context.mjs"
6
11
  import { toOpenAISchema } from "../tools/index.mjs"
@@ -120,11 +125,6 @@ export async function prepareRun(agent, input, callbacks, {
120
125
  }
121
126
  }
122
127
  if (depth === 0) {
123
- agent.history.push({
124
- role: "user",
125
- content: `[System reminder: current time is ${new Date().toISOString()}.]`,
126
- transient: true,
127
- })
128
128
  // Checklist injection: inject pending + in_progress items from .thincoder/checklist.md
129
129
  try {
130
130
  const { pendingItems } = await import("../tools/checklist.mjs")
@@ -139,6 +139,15 @@ export async function prepareRun(agent, input, callbacks, {
139
139
  } catch { /* checklist not available — suppress error */ }
140
140
  }
141
141
  pushReal(agent, { role: "user", content: input })
142
+ // Time grounding for EVERY agent depth, pushed LAST (after the user input): transient,
143
+ // dropped on persist, fresh at every run start. Tail position keeps the second-precision
144
+ // content out of any prefix — caches stay hit (plugin hit a position-drift regression
145
+ // when the reminder was interleaved before the input; CLI is aligned preemptively).
146
+ agent.history.push({
147
+ role: "user",
148
+ content: `[System reminder: current time is ${timeNowLocal()} (local; timezone ${Intl.DateTimeFormat().resolvedOptions().timeZone || "local"}).]`,
149
+ transient: true,
150
+ })
142
151
  }
143
152
 
144
153
  if (agent._pendingReminders.length > 0) {
@@ -221,14 +230,10 @@ export async function prepareRun(agent, input, callbacks, {
221
230
  ? `${base}\n\n${mainOverlay}`
222
231
  : base
223
232
 
224
- // Local time + timezone on every agent (main, subagent, consult) without it "today"/
225
- // "just now"/"recent" in user messages and search freshness are ungrounded.
226
- // MINUTE precision, deliberately: system prompts must stay byte-identical across runs
227
- // within the same minute or provider prefix caches (DeepSeek cache_hit) never hit.
228
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local"
229
- const now = new Date()
230
- const mins = String(now.getMinutes()).padStart(2, "0")
231
- systemPrompt += `\n\nCurrent time: ${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${mins} (${timeZone}).`
233
+ // Time injection deliberately does NOT live here: system prompts must be byte-identical
234
+ // across runs (provider prefix caches). The time rides a transient user reminder per turn
235
+ // (see the current-time injection below) variable content belongs in the history, not
236
+ // the cached prefix.
232
237
 
233
238
  const projectRules = await loadProjectInstructions(agent.cwd)
234
239
  if (projectRules) {
@@ -240,7 +240,9 @@ export function normalizeToolPairing(messages) {
240
240
  rest.push(m)
241
241
  }
242
242
  }
243
- if (toolById.size === 0) return messages // no tool messages nothing to enforce
243
+ if (toolById.size === 0 && !messages.some((m) => m.role === "assistant" && m.tool_calls?.length)) {
244
+ return messages // no tool messages AND no tool_calls declared — nothing to enforce
245
+ }
244
246
  const out = []
245
247
  for (const m of rest) {
246
248
  out.push(m)
package/src/session.mjs CHANGED
@@ -325,8 +325,14 @@ export function saveSession(agent) {
325
325
  // _fullHistory is written at the source via pushReal — no flush needed here.
326
326
  // history = FULL, never-compacted (human-readable; VS Code panel & CLI resume read this)
327
327
  // contextHistory = machine context (possibly compacted) so CLI resume keeps the token savings
328
+ // Human line: transient machine injections never enter the readable record.
328
329
  const history = (agent._fullHistory ?? agent.history).filter((m) => !m.transient && !isLegacyTransient(m))
329
- const contextHistory = agent.history.filter((m) => !m.transient && !isLegacyTransient(m))
330
+ // Machine line (contextHistory): KEEP transient messages — resume must rebuild the
331
+ // machine line byte-identical to what the provider cache saw. Dropping them made every
332
+ // process restart diverge at the first injection position (git/OS/time reminders are
333
+ // re-injected with FRESH content) → whole-prefix cache miss on the CLI's very first
334
+ // request of each session (2026-08-16 cache-hit report; Kimi review).
335
+ const contextHistory = agent.history.filter((m) => !isLegacyTransient(m))
330
336
  const data = {
331
337
  version: 2,
332
338
  cwd: agent.cwd,