Commit graph

20 commits

Author SHA1 Message Date
qer
76c643bcb6
feat: cap completion tokens to remaining context window for chat-completions (#1131)
* feat: cap completion tokens to remaining context window for chat-completions

* test: cover dynamic completion budget for kimi and openai-legacy

* fix: leave compaction budget uncapped to avoid one-token summaries

* fix: add hookCount to plugins-selector test mocks
2026-06-26 19:12:04 +08:00
liruifengv
2db5fc20ec
feat: add shell mode (!) to the CLI (#1079)
* feat: add shell mode (`!`) to the CLI

Add shell mode, letting users run shell commands directly from the prompt
with `!`. Output streams live into the transcript, supports backgrounding
(ctrl+b), cancellation (Esc / Ctrl+C), input queuing while running, and
enters the conversation context with resume support.

* feat(kimi-code): show shell mode label on editor border and add tip

Render a "! shell mode" label on the top-left of the editor border while the editor is in `!` bash mode, so the active mode is visible at a glance. Also add a rotating toolbar tip (`! to run a shell command`) to surface the feature.

* feat(kimi-code): refine shell mode queue, history, and display

- Keep `!` commands out of input history so they never resurface as bare text stripped of their `!`.

- Make `!` commands non-steerable: Ctrl-S skips them (they stay queued to run after the current task) and the steer hint is only shown when something is actually steerable.

- Render queued `!` commands with a `$` prompt and the shell-mode hue so they read as commands, not as text to send to the model.

- Echo executed shell commands with a `$` prompt instead of `!`.

* fix(kimi-code): sanitize shell output and harden rendering

Captured shell command output can contain terminal control sequences (colours, cursor moves, alternate-screen switches, OSC hyperlinks, carriage-return spinners, bells). pi-tui's Text passes strings straight to the terminal, so any unhandled sequence was executed by the terminal and fought with pi-tui's own cursor control, producing the blank-screen-plus-leftover-characters mess after running commands like pnpm dev or a nested TUI.

- Sanitize CSI (incl. private modes), OSC, single-char ESC and C0 control chars (keeping newline and tab) in both the finished/resume view (previously unsanitized) and the running tail.

- Make the sanitize, format, and ShellRunComponent render paths never-throw, and cap the live running buffer, so a misbehaving command cannot crash the TUI.

- Dispose transcript children on clear so ShellRunComponent's timer is released on /clear or session switch.

* fix(kimi-code): render shell command echo with $ instead of sparkles

The shell command echo is a 'user' transcript entry, so UserMessageComponent prefixed it with the USER_MESSAGE_BULLET (sparkles), producing 'sparkles $ command'.

Add an optional bullet override to UserMessageComponent / TranscriptEntry and set it to an empty string for the shell echo (both live and resume), so the '$ command' content sits at the leading column where the sparkles marker used to be. Normal user messages keep the sparkles bullet.

* fix(kimi-code): enter shell mode when pasting a !-prefixed command

The bash-mode trigger only handled the single ! keystroke, so a pasted !cmd was inserted as literal text in prompt mode and submitted as a normal message.

After pi-tui inserts pasted content, detect an empty-prompt buffer that now starts with !, switch to bash mode, and strip the leading ! so the buffer holds only the command, matching the typed ! path.

* fix(kimi-code): restore shell mode when recalling a queued command

recallLastQueued() dropped the queued item's mode, and the Up-arrow recall only restored the text. A queued ! command (queued while another command runs, which resets the editor to prompt mode) therefore came back as a normal prompt and was submitted as a message instead of a shell command.

Return the full QueuedMessage from recallLastQueued() and restore editor.inputMode (plus the onInputModeChange sync) from the recalled item's mode.

* feat(kimi-code): use violet as the shell mode color

Replace the claude-code-style magenta/rose shellMode token with a violet that is distinct from plan-mode blue, the user role amber, success green, error red, and the teal accent.

Custom themes that omit the token fall back to this new default via the base+overrides merge, so existing custom themes keep working unchanged.

* chore: refine the shell mode changeset

* docs: document shell mode

Add a Shell mode section to the interaction guide and list the ! and Ctrl+B shortcuts in the keyboard reference, in both English and Chinese.

* test(protocol): include shell events in volatile classification check

shell.output and shell.started were added as volatile event types for shell mode; update the snapshot test's volatile-type list and count accordingly.

* fix(agent-core): surface shell command failure reason with no output

When a ! shell command fails without producing stdout/stderr (non-zero exit with no output, timeout, spawn failure), the failure reason lived only in the tool result's output and the TUI showed '(no output)'. Fold it into stderr so the live view and replay show what went wrong.

* fix(kimi-code): decode CSI-u ! to enter shell mode

In terminals with the Kitty keyboard protocol (VSCode integrated terminal, Kitty), pressing ! arrives as a CSI-u sequence, so the raw normalized === '!' comparison never matched and shell mode could not be entered by typing !. Decode with printableChar before comparing, matching every other printable-key check in the TUI.

* fix(kimi-code): do not steer while a shell command is running

Ctrl-S steers queued input into the running turn, but a shell command is not an agent turn, so steering during streamingPhase === 'shell' would launch a turn before the command output is recorded. Keep Ctrl-S a no-op during shell runs; queued messages stay queued.

* fix(agent-core): escape bash tag delimiters in shell output

Shell command output is arbitrary text; if it contains a bash tag delimiter such as </bash-stdout>, the recorded pseudo-XML wrapper breaks and replay extracts the wrong slice. Escape the content when wrapping it in agent-core and unescape when extracting during replay, so output survives round-trip intact.

* docs: document the shellMode theme token

The shellMode color token was added to the palette but not propagated to its mirrors. Add it to the custom-theme docs token table, the theme JSON schema, and the custom-theme skill token list.

* feat(agent-core): reset background task deadline on detach

Add a resettable deadline timer to BackgroundManager and let tasks register a detach timeout; when a foreground task is moved to the background, its deadline resets to the background default counted from the detach moment.

Wire this into shell mode so ! commands run with a 3-minute foreground timeout and get 10 minutes once detached to the background, instead of staying bounded by the original 60-second foreground deadline.

* feat(agent-core): lower shell mode foreground timeout to 2 minutes
2026-06-25 21:24:53 +08:00
_Kerman
ba64072559
feat: detach foreground tasks to background (#821)
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
Nix Build / Check flake.nix workspace sync (push) Has been cancelled
Release / Release (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Nix Build / nix build .#kimi-code (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
2026-06-20 21:24:22 +08:00
_Kerman
7b5b818815
fix: continue compaction while context remains blocked (#813) 2026-06-16 16:48:27 +08:00
7Sageer
0927f79883
fix: cancel active turns during session shutdown (#661)
* fix: cancel active turns during session shutdown

* fix: preserve background agents during session close
2026-06-11 20:52:50 +08:00
_Kerman
72c4b0adaa
feat: agent swarm (#424) 2026-06-08 14:26:56 +08:00
Kai
4d113949c8
feat: honor HTTP_PROXY/HTTPS_PROXY/NO_PROXY for all outbound traffic (#487)
* feat: honor HTTP_PROXY/HTTPS_PROXY/NO_PROXY for all outbound traffic

Install a global undici dispatcher at CLI startup so every in-process fetch
(LLM APIs, MCP HTTP, web tools, telemetry, sign-in, update checks) honors the
standard proxy variables, and propagate NODE_USE_ENV_PROXY to spawned stdio
MCP child processes. Loopback hosts always bypass the proxy; an invalid proxy
URL is reported and ignored rather than aborting startup.

* feat: support SOCKS proxies via ALL_PROXY

Recognize SOCKS proxies (socks5/socks5h/socks4/socks alias) from ALL_PROXY or a
socks-scheme HTTP(S)_PROXY, routing traffic through a custom undici connector
backed by the socks client (reusing undici's own TLS handling for https).
HTTP(S) proxies keep precedence; NO_PROXY and loopback are honored for the SOCKS
path too. Child stdio MCP node processes honor HTTP(S) proxies via
NODE_USE_ENV_PROXY; SOCKS applies to the main process only.

* fix: address proxy review comments (env masking, child NO_PROXY, nix hash)

- Resolve HTTP(S)_PROXY explicitly via the first non-blank casing so a blank
  lowercase var can no longer mask a populated uppercase one (the dispatcher
  installed but went direct), and coerce a SOCKS-scheme value sitting in an
  HTTP(S) var to '' so it is never handed to EnvHttpProxyAgent.
- Reconcile a child's NO_PROXY override across both casings using the first
  non-blank value run through resolveNoProxy, so a per-server config override
  is not shadowed by the injected lowercase value, keeps the loopback bypass,
  and passes '*' through verbatim.
- Update flake.nix pnpmDeps hash for the added socks/undici dependencies.

* fix(proxy): honor http ALL_PROXY, match port-qualified NO_PROXY, note child Node version

- Honor an http-scheme ALL_PROXY as the catch-all fallback for both http and
  https (scheme-specific HTTP(S)_PROXY still wins), so an ALL_PROXY-only setup
  no longer installs a no-op dispatcher and connects direct.
- Make the SOCKS-path NO_PROXY matcher port-aware: a `host:port` entry now
  matches only that port (with IPv6-safe parsing for `::1` / `[::1]:443`).
- Document that child stdio MCP proxying via NODE_USE_ENV_PROXY only applies on
  Node versions that support it (>= 22.21 / >= 24.5).

* fix(proxy): IPv6 + wildcard NO_PROXY and per-server child proxy edges

- Strip IPv6 brackets from a SOCKS proxy host (e.g. ALL_PROXY=socks5://[::1]:1080)
  so the socks client connects to the bare address.
- Add the bracketed [::1] to the loopback bypass: undici's EnvHttpProxyAgent
  only exempts IPv6 loopback when the NO_PROXY entry is bracketed (it mis-parses
  bare ::1). The SOCKS-path matcher normalizes brackets on both sides.
- Match *.domain wildcard (and host:port) NO_PROXY entries in the SOCKS matcher.
- Compute the child stdio proxy env from the MERGED env so a proxy declared only
  in a server's config.env also enables NODE_USE_ENV_PROXY.

* fix(proxy): synthesize HTTP(S)_PROXY from ALL_PROXY for child processes

proxyEnvForChild now hands spawned stdio MCP children the resolved
HTTP_PROXY/HTTPS_PROXY (in both casings), synthesizing them from an http-scheme
ALL_PROXY when no scheme-specific variable is set. Node's --use-env-proxy reads
HTTP_PROXY/HTTPS_PROXY (not ALL_PROXY), so an ALL_PROXY-only parent now proxies
the child consistently with the main process. Shared resolveHttpProxyUrls helper
is reused by createProxyDispatcher and proxyEnvForChild.

* chore(changeset): tighten proxy changeset wording
2026-06-06 01:44:54 +08:00
Luyu Cheng
6a2252343a
fix: simplify goal budget schema and fix output caps (#365) 2026-06-03 15:55:14 +08:00
_Kerman
573c56e829
refactor: background task manager persistence (#285) 2026-06-02 12:48:31 +08:00
zha-ji-tui
0071b63fc8
fix(skill): wrap slash-activated skill content in system-reminder tags (#135) 2026-06-02 11:20:54 +08:00
_Kerman
1084f1d217
feat: implement MicroCompaction (#219) 2026-06-01 14:31:50 +08:00
Kai
933cf6727e
fix(agent-core): surface user interruptions to the model instead of a neutral abort (#236)
When the user interrupts running tools or parallel subagents, the tool_result
fed back to the model was a neutral `Tool "X" was aborted` or a weak "stopped by
the user", so the model treated it as a system fault and speculated about
capacity/concurrency limits instead of recognizing a deliberate stop.

Carry a UserCancellationError as the AbortSignal reason from the cancel sites
(Turn.cancel/abortTurn, SessionSubagentHost.cancelAll) through to the message
sites (tool-call settle paths and the AgentTool catches), which now emit an
explicit "deliberate user action, not a system error/timeout/capacity limit"
message. Aborts propagated from another signal (e.g. a subagent's deadline via
waitForCurrentTurn) carry their original reason, so a timeout is not mislabeled
as a user interruption. The telemetry outcome classifier matches the new
"manually interrupted" phrase to keep counting these as cancelled.
2026-05-30 09:53:21 +08:00
Haozhe
971fce6e52
feat(agent-core): add session-scoped cron tools with persistence (#157)
* feat(agent-core): add cron ClockSources abstraction

ClockSources splits wall-clock and monotonic time so the cron scheduler
can be driven by an injected/simulated clock without breaking the lock
heartbeat. resolveClockSources reads KIMI_CRON_CLOCK to switch between
system, env-var-backed, and file-backed wall clocks; monotonic time is
always process.hrtime.bigint() and never overridable.

* feat(agent-core): add 5-field cron expression parser

parseCronExpression handles the standard 5-field syntax with the
cron-style dom/dow OR rule. computeNextCronRun uses field-by-field
jumping (not minute scanning) so sparse expressions like '0 12 1 1 *'
stay fast. hasFireWithinYears caps the search at 5 years so syntactically
legal but never-firing expressions ('0 0 31 2 *') return null instead of
spinning forever — required by CronCreate validation.

* feat(agent-core): add deterministic cron jitter

Recurring jobs shift forward by min(10% of period, 15min); one-shot
jobs landing on :00/:30 shift earlier by up to 90s. Offset is hashed
from task.id so reschedules and restarts stay stable. KIMI_CRON_NO_JITTER
disables both branches for reproducible benches.

* feat(agent-core): add CronTask type and cron prompt origins

CronTask matches what gets persisted to tasks.json (with durable stripped).
CronJobOrigin carries coalescedCount and stale so the agent can react to
collapsed fires without separate channels; CronMissedOrigin tags the
boot-time AskUserQuestion path.

* test(agent-core): guard against Date.now() in cron scheduler files

oxlint 1.59 does not support no-restricted-syntax, so the ESLint-style
guard from the plan is implemented as a vitest scan. The four guarded
files (scheduler/persist/lock/jitter) must route every wall-clock read
through ClockSources.wallNow(); clock.ts is excluded because that is
where the abstraction is defined. Non-existent files are skipped so the
guard activates automatically when later commits introduce them.

* feat(agent-core): add cron telemetry event-name constants

Four event names emitted by later commits (cron_scheduled, cron_fired,
cron_missed, cron_deleted) live with the cron module rather than in the
generic telemetry interface so a typo can't drift the metric and the
abstraction stays domain-free.

* feat(agent-core): add in-memory SessionCronStore

Holds cron tasks scoped to a single CLI session — vanish on exit. Phase 2
will add a file-backed sibling that shares the shape. Ids are 8 hex
characters with a collision-retry cap; createdAt is supplied by the
caller's wall clock so the store stays clock-pure (and exempt from the
Date.now() guard for the same reason).

* feat(agent-core): add session-only CronScheduler engine

The scheduler is a pure callback-driven loop: it gets tasks from a
source(), gates on isIdle()/isKilled?(), computes next fire via
cron-expr + jitter, and invokes onFire with the coalesced count when a
task is due. lastSeenAt is in-memory only — coalesce semantics make a
restart skip acceptable, but persisting last-fire would silently swallow
legitimately-due fires. pollIntervalMs=null lets P1.8 disable the
auto-tick timer for bench scenarios.

* feat(agent-core): add CronManager Agent integration layer

CronManager owns a SessionCronStore + CronScheduler and wires them to
the Agent: scheduler.isIdle reads agent.turn.hasActiveTurn, isKilled
reads KIMI_DISABLE_CRON, onFire builds a CronJobOrigin and routes
through agent.turn.steer. Stale flag is computed on read (7-day age,
recurring only, KIMI_CRON_NO_STALE shorts it) so manager doesn't have
to mutate persisted tasks. handleMissed takes a renderer callback so
P2.7 can plug in the AskUserQuestion text without bringing render
imports into Phase 1.

* feat(agent-core): add CronCreate tool (session-only path)

CronCreate validates the expression, enforces the 5-year fire window
(blocking '0 0 31 2 *' typos), caps prompt bytes at 8KB, caps active
jobs at 50 per session, and rejects durable=true until Phase 2 adds the
file-backed store. Manager exposes emitScheduled/emitDeleted so tools
never reach into agent.telemetry directly.

* feat(agent-core): add CronList tool

Read-only tool surfacing every scheduled cron job for the session. Each
record carries id / cron / humanSchedule / nextFireAt / recurring /
durable / ageDays / stale, formatted in the same key: value\n---\n
shape as TaskList. nextFireAt is the post-jitter timestamp so the model
sees what the scheduler will actually fire on. Malformed cron strings
render with null nextFireAt instead of throwing — defends against any
future direct store inserts.

* feat(agent-core): add CronDelete tool

Validates the 8-hex id shape up front and routes deletion through the
manager so cron_deleted telemetry stays consistent with cron_scheduled.
Not-found is reported as an error so the model corrects itself rather
than silently believing the delete succeeded — the next CronList would
still show whatever id was missed.

* feat(agent-core): wire CronManager + cron tools into Agent

Agent gains a public cron field constructed and started in the
constructor. The scheduler's setInterval is .unref()'d so the cron
timer never keeps the process alive, and isKilled (KIMI_DISABLE_CRON)
short-circuits every tick, so eager start is safe.

ToolManager registers CronCreate/CronList/CronDelete next to the
background tools. initializeBuiltinTools runs lazily after the Agent
constructor finishes, so this.agent.cron is already defined when the
tools are constructed.

* feat(agent-core): add manual-tick env + SIGUSR1 bench hook

KIMI_CRON_MANUAL_TICK=1 forces the scheduler into manual-drive mode
(pollIntervalMs: null), and in the same gate SIGUSR1 binds to a
no-throw manager.tick() so bench scripts can advance the scheduler
with kill -USR1 <pid> without a custom RPC.

SIGUSR1 binding is opt-in (rather than always-on) for two reasons:
the auto-tick interval already advances the scheduler, and a CLI with
many subagents would otherwise pile up listeners and trip Node's
10-listener default. Tests cover the env gate, signal swallowing,
listener cleanup, and the no-bind path when the env is unset. The
AgentTestContext harness gains an onTestFinished cleanup so the
auto-started cron manager never leaks across test files.

* test(agent-core): add end-to-end session cron smoke

Exercises the full Agent → ToolManager → CronScheduler stack through
the production CronCreateTool surface. Local-time anchor + injected
ClockSources make coalescedCount=3 deterministic across host
timezones. A second case walks the Create → List → Delete tool cycle
to confirm the three-tool surface composes round-trip.

* test(agent-core): extract shared cron test harness

Pulls the duplicated createAgentStub + createClocks helpers out of the
five cron test files into test/agent/cron/harness/stub.ts. The shared
stub keeps the lightweight-Agent shape (only turn + telemetry surfaces
need to look real) while letting individual tests opt into the
options that mattered locally (hasActiveTurn / steerReturns).

* test(agent-core): trim cron test file headers

Compresses each cron test header to a couple of lines describing what
the file covers. The long rationale blocks were process documentation
(why-this-file-exists, why-stub-vs-real-agent, plan-doc references)
that didn't help anyone reading the test later. The few details that
mattered (local-time anchor, coalescedCount math) stayed inline next
to the code that needs them. E2E test also picks up the shared
createClocks helper instead of defining its own.

* test(agent-core): convert cron tool output assertions to inline snapshots

The 21 multi-field toMatch / toContain assertions across CronCreate /
CronList / CronDelete tests covered the same ground a snapshot would
have but cost more diff churn when a format detail changes. Errors
become single-line snapshots; success outputs go through a small
scrubCronOutput helper that replaces the random 8-hex id and ISO
timestamp with stable placeholders so the snapshot is deterministic
across TZ and run.

* refactor(cron): remove durable flag, env clock source, and enable cron tools in default profile

- Remove the unimplemented durable persistence field from the entire cron stack (types, tools, manager, tests, docs) to avoid misleading the model into promising cross-session persistence that does not exist yet.
- Drop the env:VAR clock source in favour of the file:path source for test/bench control.
- Register CronCreate, CronList, and CronDelete in the default agent profile.

* fix(agent-core): address PR #136 typecheck and Codex review for cron tools

Typecheck:
- Bracket-access KIMI_CRON_* env reads under TS4111 in cron source/tests.
- Add `approvalRule` to CronCreate/CronList/CronDelete tool executions.
- Guard `cron-expr.detectStep` against undefined array elements; cast the
  readonly snapshot in session-store.test through `unknown`; import the
  missing `ClockSources`/`ContentPart` symbols in manager.test.
- Extend `isReplayUserTurnRecord` switch to cover the new `cron_job` and
  `cron_missed` origins so kimi-code stays exhaustive.

Semantics (from Codex review):
- CronCreate re-reads `wallNow()` and re-checks the session cap inside
  `execute()`, so manual-approval delays and concurrent prepared calls
  can't backdate the schedule or breach the cap.
- One-shot jitter floors the pull-forward at `task.createdAt`, so a
  brand-new `:00`/`:30` reminder can't end up before its scheduling time.
- Scheduler coalesce loop reapplies the same jitter as the delivery path
  and advances `lastSeenAt` to the last actually-delivered ideal fire;
  a not-yet-due jittered slot is no longer lost. One-shot fires always
  report `coalescedCount: 1`.
- Manager removes recurring tasks after the first stale fire and emits a
  `cron_deleted` event, matching the 7-day auto-expire contract.
- CronList anchors one-shot `nextFireAt` at `createdAt`, so a pending
  today's slot isn't rendered as tomorrow.

Tests cover each of the above and a changeset is added.

* fix(agent-core,tui): address deep review + Codex review on PR #136

Closes the deep-review pass and the four Codex review rounds that
followed on the cron tools feature. Consolidated rather than landed
as a series so the PR history reads as one fix wave on top of the
original Phase-1 cron implementation.

## Lifecycle + structural

- Session.close() now stops every agent's CronManager via
  `Promise.allSettled` (mirroring `stopBackgroundTasksOnExit`).
  Without this the 1s setInterval and its closure-captured
  Agent/Session graph leaked on every closed session.
- Agent constructor gates `cron.start()` and the three Cron tool
  instantiations on `type !== 'sub'`. Subagents no longer pile up
  empty 1Hz timers or duplicate SIGUSR1 listeners under
  `KIMI_CRON_MANUAL_TICK=1`.

## Permission + plan-mode parity

- `PlanModeGuardDenyPermissionPolicy` denies CronCreate and
  CronDelete during plan mode (CronList stays allowed); matches the
  TaskStop precedent.
- `DEFAULT_APPROVE_TOOLS` includes CronList for parity with TaskList
  / TaskOutput so manual-approval mode doesn't prompt on read-only
  listings.

## Cron-fire envelope + projector

- Cron fires wrap `task.prompt` in a `<cron-fire jobId=… cron=…
  recurring=… coalescedCount=… stale=…><prompt>…</prompt></cron-fire>`
  XML envelope (mirrors `notification-xml.ts`). Without this the
  `coalescedCount` and `stale` cues documented in cron-create.md
  were invisible to the LLM.
- Projector `isInjectionUserMessage` recognises `<cron-fire ` so the
  envelope isn't merged into adjacent real user messages.
- TUI `SessionReplayController.renderUserMessage` skips cron_job /
  cron_missed origins (matches `isReplayUserTurnRecord`'s exclusion);
  resumed sessions no longer render the raw envelope as user text or
  miscount cron records toward the replay turn limit.

## Scheduler invariants

- `tick()` only advances `lastSeenAt` / removes one-shots after a
  successful `onFire`. A throw in `agent.turn.steer` previously
  silently lost the fire; now the next tick re-detects and retries.
- New `getNextFireForTask(id)` on the scheduler (delegated through
  the manager) lets CronList render the same instant the scheduler
  will fire. Previously CronList computed from `nowMs` and could
  report tomorrow's slot while a current-period jittered delivery
  was still pending.

## Cron parser + validation

- `parseCronExpression` now rejects non-cron numeric forms via a
  `parseCronInt` helper guarded by `^\d+$` — `''` / `'1e1'` /
  `'0x10'` / `'+5'` / `'-5'` no longer silently become 0, 10, 16,
  5, or `0-5`.
- `nextRunWithinMinutes` bounds search by a wall-time deadline
  instead of iteration count. Each iteration can skip a month, so
  the old `capMinutes + 10_000` cap let `0 0 30 2 *` walk ~200 000
  years before bailing; the new path returns null in microseconds.
- `oneShotJitteredNextCronRunMs` returns `idealMs` (not `createdAt`)
  when the pulled-forward time would precede `createdAt`. The old
  clamp made an 08:59:30-scheduled `0 9 * * *` fire on the very next
  tick — ~29 s before ideal — instead of at 09:00.

## CronList + CronCreate output

- CronList output adds a `prompt:` row, JSON-encoded so newlines
  stay on one line, truncated to ~200 UTF-8 bytes on a char
  boundary. The model can recall a task's intent after compaction
  and use it as the source for the documented refresh ritual.
- CronCreate normalizes `args.cron` whitespace BEFORE
  `parseCronExpression` so `parsed.raw` is single-line. Otherwise
  inputs like `"1\n2\n3\n4\n5"` (legal — parser accepts any \s+)
  produced a multi-line `humanSchedule:` row via the cronToHuman
  raw-fallback branch.

## Misc hardening

- `KIMI_CRON_CLOCK=file:<path>` reads at most 64 bytes via
  `openSync` + `readSync` so a stray-large file can't OOM.
- SIGUSR1 handler logs swallowed `tick()` exceptions to stderr when
  `KIMI_CRON_DEBUG=1` (matches scheduler's debug pattern); silent in
  production.
- Documentation rewrite across cron-list.md / cron-create.md /
  cron-delete.md so the documented stale + nextFireAt behaviour
  matches the implementation (recurring stale tasks auto-delete after
  the final fire; `nextFireAt` is an ISO timestamp; refresh ritual
  is "just CronCreate again — the old id is already gone").

Tests cover each of the above. Suite at 2090+ passing across
agent-core, kosong, and the kimi-code app; typecheck clean across
all workspace packages.

* feat(cron): persist scheduled tasks across kimi resume

Add per-id JSON persistence so cron tasks survive a kimi resume of the same session.

Core changes:
- CronManager: addTask / removeTasks mirror mutations to <sessionDir>/cron/<id>.json
- CronManager.loadFromDisk() rehydrates the in-memory store on resume
- CronManager.flushPersist() drains pending writes for graceful shutdown
- SessionCronStore.adopt() inserts persisted tasks with original id and createdAt
- Extract shared createPerIdJsonStore utility from background/persist.ts
- Refactor background/persist.ts to use createPerIdJsonStore (no behavior change)
- New tests: resume.test.ts, persist.test.ts, per-id-json-store.test.ts
- Update cron-create / cron-list / cron-delete docs to reflect session lifetime

* fix(agent-core): fix cron-stop-on-close test import for tsgo compat

* feat(cron): persist lastFiredAt cursor and scope approval rules

- persist lastFiredAt across resume so recurring tasks don't replay
- add one-shot pinned-date guard to reject >1-year-out first fires
- scope CronCreate approvalRule to exact payload (cron, prompt, recurring)
- add resume replay tests and corrupt-cursor fallback test
- stop cron before awaiting background shutdown so due ticks cannot
  start a fresh turn while session.close() is mid-flight
- filter cron_job / cron_missed origins from markdown export so the
  internal <cron-fire ...> envelope no longer leaks into user-facing
  exports
2026-05-28 21:05:46 +08:00
_Kerman
36add70d57
refactor(kaos): move Environment into kaos, slim package API (#147) 2026-05-28 16:50:10 +08:00
_Kerman
8515472476
fix(agent-core): compaction edge cases, error handling, and truncated output (#120) 2026-05-28 16:47:38 +08:00
_Kerman
e5717b7261
refactor: unify path normalization with pathe (#84) 2026-05-27 11:53:24 +08:00
qer
2bb50a38d8
fix: avoid local completion budget truncation (#85) 2026-05-26 22:25:15 +08:00
_Kerman
c4dd1c7ff2
feat: flatten tool call records (#25) 2026-05-25 23:02:25 +08:00
qer
bfbd522a71
fix: widen Kimi completion budget (#17) 2026-05-25 18:41:59 +08:00
Kaiyi
842e699a64 Kimi For Coding 2026-05-22 15:54:50 +08:00