@epoch-agent/infra 0.2.0 → 0.3.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.
@@ -249,6 +249,10 @@ web:
249
249
  mkdir_parent_not_absolute: 'the location to create in must be an absolute path: {parent}'
250
250
  mkdir_bad_name: 'the directory name must be a single segment with no path separators, and cannot be . or ..: {name}'
251
251
  mkdir_failed: 'could not create that directory: {path}'
252
+ pick_prompt: Choose a workspace directory
253
+ pick_unavailable: 'this machine cannot open a system directory dialog: it is only enabled when the server and the browser are on the same machine (loopback bind, not started over ssh, macOS or Windows) — use the built-in browser instead'
254
+ pick_busy: a directory dialog is already open — go answer it (this window may be covering it)
255
+ pick_failed: 'the system directory dialog could not be opened: {detail}'
252
256
  plan_mode_unavailable: this process has no plan mode (the permission layer did not start)
253
257
  plan_mode_bad_action: action must be one of {actions}
254
258
  permission_unavailable: this process has no permission layer, so the level cannot be changed (what you see is the value from the config file)
@@ -260,7 +264,7 @@ web:
260
264
  rewind_bad_overwrite: overwrite must be a list of file paths (the ones the user confirmed one by one); omit the field entirely if there is nothing to overwrite
261
265
  rewind_no_checkpoint: no checkpoint for turn {turn}
262
266
  settings_write_bad_request: the body needs key (the dotted path of the setting), layer (one of {layers}) and value (string / number / boolean)
263
- settings_write_unknown_key: that key cannot be edited here. This path writes model and maxTurns onlyevery other setting has just the built-in and user layers, and putting it in a settings file does nothing
267
+ settings_write_unknown_key: 'that key cannot be written this way: either it is not on the writable list at all (the fallback chain, for one it is an ordered chain, not a scalar), or the key only writes to the user layer while the request asked for project-local. The project layers accept permissions / model / maxTurns only; anything else lands as an "unknown setting" in the startup diagnostics'
264
268
  settings_write_bad_value: that value is outside this setting's domain (maxTurns must be a positive integer, for one), so nothing was written
265
269
  settings_write_unwritable: this layer does not recognise how {path} is written (or the file is already broken), so it was left alone — please edit that file yourself
266
270
  settings_write_io: 'writing {path} failed: possibly no write permission, or the directory could not be created'
@@ -268,6 +272,8 @@ web:
268
272
  mcp_unknown_server: This process has no MCP server named "{name}" — connections are built from mcp.json at startup, so the list you are holding may be stale. Refresh and look again.
269
273
  skill_unknown: No skill named "{name}" — the capability page list may be stale. Refresh and look again.
270
274
  provider_unknown: No provider named "{name}" is supported — the selectable set is a fixed list, so the one you are holding may be stale. Refresh and look again.
275
+ provider_key_not_applicable: '{name} does not use an API key — one stored here would never be read, and that provider already works as is'
276
+ provider_key_empty: No key received — paste it into the box and save. (There is no way to revoke an already-stored key from here yet; delete it from ~/.epoch or your keychain.)
271
277
  mcp_add_lan_exposed: 'MCP servers cannot be added in this mode — the server was bound outside the loopback interface with --host, so we cannot tell whether the browser sending this request is on this machine. Adding one writes "which executable to launch" into mcp.json; that server is started right away, and every future epoch run on this machine (including the CLI and the TUI) starts it too. Two ways forward: edit that file directly on the machine running the server, or drop --host and restart.'
272
278
  mcp_add_bad_transport: Transport must be one of stdio / sse / http
273
279
  mcp_add_bad_name: The server name "{name}" cannot be used — it becomes part of the tool name verbatim, so only names starting with a letter or digit and made of letters, digits, _ and - are accepted (48 max). A hand-written mcp.json is not subject to this; only names added from the UI are.
@@ -301,6 +307,17 @@ web:
301
307
  reference_store_unavailable: This machine has no session database (SQLite did not come up), so no historical session can be referenced
302
308
  file_candidates_no_workspace: This session has no workspace bound, so no file list can be offered — pick a directory from the bar under the input box first
303
309
  file_list_unavailable: The workspace file list could not be produced (no .git and not a readable directory), so completion is unavailable — typing the path still attaches the file
310
+ file_view_no_workspace: This session has no workspace bound, so no file can be shown — pick a directory from the bar under the input box first
311
+ file_view_missing_path: No file was named (path is missing)
312
+ file_view_denied: That file is outside the workspace, or a permission rule blocks it — what the model cannot read is not readable here either
313
+ file_view_missing: That file is no longer there
314
+ file_view_not_a_file: That is a directory, not a file
315
+ file_view_unreadable: That file could not be read (permission bits, a write in progress, or a disk problem)
316
+ file_view_too_large: That file is too large to view in place (limit {max} bytes) — open it with the default app, or download it
317
+ file_view_not_text: That is not a text file, so there is no body to fetch — images, PDFs and media go through the bytes route
318
+ file_open_extension_not_allowed: That kind of file is not handed to a system app — code and text are viewable in place, and "opening" an archive would extract it
319
+ file_open_not_same_machine: This browser is not on the machine running the server, so any window would open on someone else screen
320
+ file_open_launch_failed: The system app did not start
304
321
  ui:
305
322
  conn:
306
323
  connecting: Connecting
@@ -327,10 +344,14 @@ web:
327
344
  cav_no_tools: switched, but {model} does not support tool calls — it can only chat, and cannot read or write files
328
345
  pick_provider: Provider
329
346
  pick_provider_none: Pick one…
347
+ pick_model: Model name
348
+ suggest_open: See which models were found
330
349
  provider_no_key: '{label} (no key configured)'
331
350
  probe_probing: Asking {provider} which models it has… no need to wait — you can just type one above.
332
351
  probe_no_key: No key configured for {provider}, so its model list cannot be fetched. Set {env}, or run epoch model.
333
352
  probe_no_key_bare: No key configured for {provider}, so its model list cannot be fetched. Run epoch model to set one up.
353
+ probe_no_key_here: No key configured for {provider}, so its model list cannot be fetched — fill in the API Key box above.
354
+ key_goto: Configure it in settings
334
355
  probe_failed_cache: Could not reach {provider} (network / bad key / that base URL has no /models). Below is what was fetched last time, which may be stale — you can also just type a name.
335
356
  probe_failed_static: Could not reach {provider} (network / bad key / that base URL has no /models). Below is the built-in list, which may be stale — you can also just type a name.
336
357
  probe_empty: '{provider} returned no model names at all, and the built-in list is empty too — just type one.'
@@ -338,19 +359,34 @@ web:
338
359
  suggest_note: These are fetched suggestions, not the full set — new or self-hosted models will not be here. You can still just type one.
339
360
  refresh: Re-fetch
340
361
  refresh_hint: Bypass the cache and ask this provider again
362
+ key_label: API Key
363
+ key_placeholder: Paste a key here
364
+ key_save: Save key
365
+ key_saving: Saving key…
366
+ key_show: Show
367
+ key_hide: Hide
368
+ key_has: 'Configured: {hint} ({env}). Saving a new one overwrites it.'
369
+ key_none: This key goes into the system keychain when one is available, otherwise into ~/.epoch/.env (mode 0600). It is stored as {env}.
370
+ key_saved_secure: Stored in {backend} (encrypted). The model list has been re-fetched with the new key.
371
+ key_saved_plain: ⚠️ The system keychain was unavailable, so this key was stored in plaintext at {path} (mode 0600). Anyone who can read that file on this machine has the key.
341
372
  newsession:
342
373
  open: New session
343
374
  dirpick:
344
375
  back: Back
345
376
  anchors: Common places
377
+ anchors_detail: Home, the server process directory, and workspaces you have visited
346
378
  up: Up one level
379
+ back_last: Back where you were
347
380
  loading: Loading…
348
381
  empty: No subdirectories here — pick this one, or type a path below
349
382
  omitted: '{count} more not listed at this level (1000 max per level) — type a path below to reach them'
350
383
  show_hidden: Show hidden directories (starting with .)
351
384
  show_hidden_count: Show hidden directories (starting with ., {count} here)
352
385
  unreadable: 'no permission to enter this directory: {path}'
353
- into: Enter {name}
386
+ into_of: Enter {name} ({path})
387
+ use: Use {name}
388
+ use_here: Use this folder
389
+ use_here_of: 'Use this folder: {path}'
354
390
  known: visited
355
391
  manual_label: Or type an absolute path
356
392
  manual_hint: e.g. /Users/you/work/repo
@@ -363,14 +399,10 @@ web:
363
399
  title: What would you like to do?
364
400
  sidebar:
365
401
  search: Search sessions
366
- capability: Identities · Skills · Connectors
367
- plugins: Plugins
368
402
  automation: Automation
369
403
  automation_pending: '{count} scheduled tasks are missing a grant; they will stall again on the next run'
370
404
  settings: Settings
371
- session: Session
372
- usage_session: Session usage
373
- usage_run: Last run usage
405
+ host_menu: Host menu
374
406
  cost: Spend
375
407
  cost_of: ~${spent} / ${limit}
376
408
  cost_est: Estimate
@@ -386,6 +418,7 @@ web:
386
418
  running: Running
387
419
  updated_at: Last active
388
420
  count: '{count} sessions'
421
+ new_in_space: New session in “{name}”
389
422
  empty: No sessions yet
390
423
  no_match: No matching sessions
391
424
  acts: Actions for “{title}”
@@ -457,6 +490,18 @@ web:
457
490
  clip_lines: Only the first {shown} lines are shown; this file has {total} lines.
458
491
  clip_chars: Cut off on line {line} — that line is too long. This file has {total} lines.
459
492
  binary_note: These files cannot be viewed here — open them with the program your system uses.
493
+ file:
494
+ loading: Opening…
495
+ denied: That file is outside the workspace, or a permission rule blocks it — what the model cannot read is not viewable here either.
496
+ missing: That file is no longer there.
497
+ not_a_file: That is a directory, not a file.
498
+ too_large: That file is too large to view here.
499
+ unreadable: That file could not be read.
500
+ no_answer: The server gave no answer for that path.
501
+ no_session: No session yet, so no file can be shown.
502
+ opaque: The browser cannot render this kind of file. Open it with the default app on your machine, or download it.
503
+ open_with_app: Open with default app
504
+ download: Download
460
505
  plan:
461
506
  empty: No approved plan in this session yet
462
507
  empty_hint: A plan the model hands in has to be approved once on the panel above the composer; only the approved one stays here.
@@ -506,7 +551,7 @@ web:
506
551
  dismiss: Got it
507
552
  session_gone: This conversation is gone on the server — most likely it restarted, and a restart starts a fresh session. This one is not coming back, but you can pick up in the new one.
508
553
  session_gone_open: Go to the current session
509
- session_gone_stuck: This conversation is gone on the server, and right now it cannot even tell us which session is still usable — the server process has most likely stopped. Check on it in your terminal.
554
+ session_gone_stuck: This conversation is gone on the server.
510
555
  thread:
511
556
  thinking_label: Reasoning
512
557
  head_estimate: expected {tokens} tokens
@@ -539,6 +584,8 @@ web:
539
584
  art_no_file: Not spooled to disk — this artifact cannot be opened.
540
585
  art_download: Download {name}
541
586
  art_download_any: Download this artifact
587
+ user_image_alt: Image attached to this message ({mediaType})
588
+ user_image_alt_any: Image attached to this message
542
589
  expand: Expand {count} more lines
543
590
  collapse: Collapse
544
591
  todo_title: Delivery checklist
@@ -622,6 +669,7 @@ web:
622
669
  fact_detail: Detail
623
670
  fact_reason: Reason
624
671
  target_none: (none)
672
+ more_lines: ↓ {count} more lines below
625
673
  allow_once: Allow once
626
674
  allow_session: Always allow this session
627
675
  allow_session_hint: until you quit
@@ -662,6 +710,7 @@ web:
662
710
  stalled: “{title}” was going in circles and stopped
663
711
  aborted: “{title}” was stopped mid-run
664
712
  filtered: “{title}” was refused by the model
713
+ compliance: “{title}” ran into a content compliance limit
665
714
  budget: “{title}” hit the budget cap and stopped early
666
715
  failed: “{title}” hit an error
667
716
  go: View
@@ -686,6 +735,9 @@ web:
686
735
  placeholder_gone: This conversation is gone on the server; there is nothing to send to
687
736
  hint_queue: to queue
688
737
  send: Send
738
+ usage_session: Session
739
+ usage_run: Last run
740
+ usage_detail: '{scope} usage: {up}↑ {down}↓'
689
741
  commands_label: Slash commands
690
742
  command_warnings: 'A few things to know about how /{name} expanded:'
691
743
  builtin_notice: /{name}
@@ -705,7 +757,8 @@ web:
705
757
  workspace_pick_note: The list is the directories recently used on this machine. To start a session somewhere new, run epoch there once first.
706
758
  workspace_browse: Open a local folder…
707
759
  workspace_browse_detail: Walk down to a directory, or type an absolute path (lists directories on the machine running epoch)
708
- permission: '{level} permissions'
760
+ workspace_browse_native: Opens a system dialog (on the machine running epoch)
761
+ permission: 'Permissions: {level}'
709
762
  plan_mode: Plan mode
710
763
  plan_on_hint: 'Enter plan mode: the model researches read-only and hands you a plan to approve; it touches no files meanwhile'
711
764
  plan_off_hint: Leave plan mode and go back to the permission level you were on
@@ -757,6 +810,10 @@ web:
757
810
  lvl_bypass: Skip every check
758
811
  lvl_bypass_detail: Nothing is ever asked, and the whole rail turns red
759
812
  blocked_managed: This machine has enterprise managed settings, and they turn off the "Skip every check" level. See Settings › Security centre for details
813
+ keep_env_pinned: 'This level applies to the current run only: EPOCH_PERMISSION overrides it, so the next start will use that instead'
814
+ keep_unwritable: 'This level applies to the current run only: your config file''s layout isn''t one this path recognises, so it was left alone'
815
+ keep_io: 'This level applies to the current run only: the config file could not be written (read-only disk, or no permission)'
816
+ keep_no_config: 'This level applies to the current run only: this process cannot tell where your user config file is'
760
817
  fatal:
761
818
  unauthorized: No access credential
762
819
  unauthorized_hint: Go back to the terminal running epoch web and reopen the tokenised URL it printed.
@@ -768,6 +825,7 @@ web:
768
825
  pane_model: Model & provider
769
826
  pane_memory: Memory & context
770
827
  pane_spend: Usage & spend
828
+ history_only: 'This session is history only — no engine is running it yet. Opening an old session just replays the conversation; the engine is built when you send your next message. This pane asks that engine for its current state, so there is nothing to show yet: go back to the session, send a message, and it will be picked back up.'
771
829
  pane_budget: Budget
772
830
  pane_tools: Tools
773
831
  pane_about: About
@@ -793,8 +851,6 @@ web:
793
851
  none_recorded: This conversation has run, but the per-turn records were never written down (the session predates the change that records them). The numbers will not come back.
794
852
  g_total: Totals
795
853
  g_turns: Per turn
796
- total: Across {turns} turns, {tokens} tokens in total, about {cost}.
797
- total_tokens: Across {turns} turns, {tokens} tokens in total.
798
854
  total_unmeasured: This session ran {turns} turns, but no usage was ever reported, so there is not even a token total.
799
855
  partial: The amount above only covers {priced} of those turns — the other {unpriced} reported usage, but the model they ran on has no pricing data. In the table below they are the rows with an empty cost cell.
800
856
  unknown: 'The amount is unknown, not zero: the models used in these turns have no pricing data on this machine, so only the provider knows how much was really spent.'
@@ -805,7 +861,7 @@ web:
805
861
  tok_output: Output
806
862
  tok_reasoning: 'Of which reasoning: {n}. It is already included in the number above.'
807
863
  tok_cache_note: Cache hits are priced separately, usually far below the full rate — the more hits, the cheaper the same amount of context.
808
- col_turn: Turn
864
+ col_turn: Turns
809
865
  col_model: Model
810
866
  col_tokens: Tokens
811
867
  col_cost: Cost
@@ -820,6 +876,7 @@ web:
820
876
  why: Why this value
821
877
  chain_win: wins
822
878
  g_other: Other
879
+ overridable_sum: Which keys upper layers can override
823
880
  overridable: 'Only these keys can be overridden by the project, project-local, command-line or managed layers: {keys}. Every other setting has just two layers, built-in and user — putting one in .epoch/settings.json does nothing, and startup diagnostics will report it as an unknown config key.'
824
881
  overridable_perm: The permissions entry on that list is three lists (allow / ask / deny) merged as a union rather than an override, so its provenance is per rule — see Security › Permission rules.
825
882
  managed_locked: This one is set by the managed settings on this machine; changing it at the user or project level has no effect. That file is not writable by an ordinary user — ask an administrator.
@@ -828,12 +885,13 @@ web:
828
885
  save: Save
829
886
  new_value: New value
830
887
  write_layer: Write to
888
+ write_file: Into file
831
889
  dirty: Unsaved
832
890
  futile_shadowed: 'Writing to {layer} will have no effect: {by} sets this too, and it ranks higher, so on load it overrides what you write here. To make it take effect, write to {by} or above.'
833
891
  futile_untrusted: 'This workspace is not trusted yet, so settings in {layer} are dropped wholesale on load (deny rules are the one exception) — writing there does nothing. Trust the directory under Security › Workspace & trust first, or write to the user layer instead: it does not go through the trust gate.'
834
892
  wrote: 'Written to {layer}: {value}'
835
893
  wrote_stale: This process read the config once at startup and never re-reads it, so it is still using {value} — restarting epoch web is what switches it to the value you just wrote. Starting a new session will not do it either; the config is one per process.
836
- lede_model: 'The model and provider this process is using right now. To change: edit ~/.epoch/config.yaml, or run epoch model.'
894
+ lede_model: The model this process is using right now. Changing a row here writes to ~/.epoch/config.yaml. The provider and API key are not on this screen — run epoch model for those.
837
895
  k_model: Main model
838
896
  k_models_utility: Utility model
839
897
  h_models_utility: Used for chores like compacting context and titling sessions. Unset means everything goes to the main model.
@@ -841,7 +899,7 @@ web:
841
899
  k_fallback_providers: Fallback chain
842
900
  h_fallback_providers: Tried in this order when the main provider is unreachable.
843
901
  lede_memory: The first two decide how much fits in a turn and how many steps it may take; the last two decide what happens when it no longer fits. Setting a context window larger than the model really has buys you a 400.
844
- k_context_length: Context window
902
+ k_context_length: Context window (tokens)
845
903
  h_context_length: Only used as a fallback when the model metadata cannot be read; when it can, metadata wins and a configured value has no effect. The compression line below uses the window the engine actually runs on.
846
904
  k_max_turns: Max steps per turn
847
905
  h_max_turns: How many tool calls the model may make in one turn. On reaching the cap it stops and finishes its answer.
@@ -852,8 +910,7 @@ web:
852
910
  ctx_line: With this configuration, compaction starts once the prompt reaches {tokens} tokens — {percent}% of the {window}-token context window.
853
911
  ctx_line_caveat: That is the line itself. Just after a compaction, or once several compactions in a row have failed, crossing it will not compact yet.
854
912
  ctx_off: Auto-compaction is off. When a long session hits the context window there is no automatic rescue — that turn simply fails, and the context does not shrink on its own. Manual /compact is unaffected by this switch and still works.
855
- ctx_no_switch: 'These two rows cannot be changed here, same as the two above: only three keys can be overridden by project-level config (the list below), and compaction is not one of them — otherwise a repository could quietly let long sessions opened inside it run into the window. Change it by editing ~/.epoch/config.yaml yourself:'
856
- ctx_restart: Restart the epoch web process for this to take effect — it reads the config once at startup and never re-reads it.
913
+ ctx_not_overridable: 'These two rows can only be written to the user level: only three keys can be overridden by project-level config (the list below), and compaction is not one of them — otherwise a repository could quietly let long sessions opened inside it run into the window.'
857
914
  budget_title: Where it is going right now
858
915
  budget_used: '{used} used — {percent}% of the {limit} window.'
859
916
  budget_used_only: '{used} used. This model has no known context window, so there is no percentage to report.'
@@ -863,13 +920,21 @@ web:
863
920
  budget_compacting: Compacting…
864
921
  budget_compact_hint: Rewrites earlier messages into a summary to free up window for what comes next. It sends one model request (so it costs money), but not a single line of this conversation is removed — only what gets sent to the model next turn changes.
865
922
  lede_budget: Amounts only appear in the UI once a cap is configured. With no cap these rows read "Not set", which is also the default.
866
- k_budget_max_cost: Spend cap
923
+ k_budget_max_cost: Spend cap (USD)
924
+ h_budget_max_cost: 'Accumulates per session — not a per-request cap — and --resume picks the count back up. The turn that crosses the line is not charged first and reported after: the gate stops it before the next request goes out.'
867
925
  k_budget_max_tokens: Token cap
868
926
  h_budget_max_tokens: With both rulers configured, the gate reports whichever is tighter.
869
927
  k_budget_warn_at: Warn at
870
928
  h_budget_warn_at: Percent. Warns once, on crossing — not every turn.
871
929
  k_budget_unknown_pricing: When the model has no pricing data
872
930
  h_budget_unknown_pricing: block stops the call, warn lets it through with a warning. With no pricing data the cost is "unknown", not 0, and the two must be handled separately.
931
+ ex_budget_max_cost: e.g. 5
932
+ ex_budget_max_tokens: e.g. 1000000
933
+ ex_budget_warn_at: e.g. 80
934
+ blocked_empty: This cannot be empty. Clearing it is not the same as removing the setting — this path has no delete-key at all; to get the default back, delete that line in the file named above.
935
+ blocked_pick: Pick one from the row above.
936
+ blocked_number: This takes a number, and "{text}" does not read as one. No unit, no thousands separators.
937
+ blocked_boolean: This takes exactly true or false — those are the two literals that go into the yaml.
873
938
  sec:
874
939
  lede: What this agent can touch right now, and what is holding it back.
875
940
  no_session: 'No session yet. Three things here are bound per session: the workspace, its trust verdict, and the project-level policy directory that follows that trust — with no session there is no "which one" to answer.'
@@ -993,7 +1058,7 @@ web:
993
1058
  audit_code_headless_unauthorized: Non-interactive, not pre-authorized
994
1059
  audit_code_user_approved: You approved it
995
1060
  audit_code_user_denied: You denied it
996
- perm_where: 'Change the top one from the row under the composer it takes effect immediately. The bottom one is where new sessions start: edit ~/.epoch/config.yaml and restart.'
1061
+ perm_where: 'Both come from the row under the composer: picking a level takes effect immediately and is recorded as the starting point below (written to ~/.epoch/config.yaml), which new sessions and the next start begin from.'
997
1062
  perm_now: Now
998
1063
  perm_default: Default
999
1064
  perm_now_changed: (changed in this session)
@@ -1065,7 +1130,7 @@ web:
1065
1130
  skill_unindexed: not in the index
1066
1131
  skills_capped_note: '{count} skills are held out of the index by `skills.maxIndexed`: they cost nothing per turn, and the model cannot list them either. Raise or remove that setting to get them back.'
1067
1132
  skills_role_note: '{count} skills are outside this session identity''s skill allowlist, so they are not indexed. ⚠️ That is not a lock: the bodies stay readable — narrowing is a context budget, not reachability.'
1068
- no_toggle: 'There is no "turn this skill off" switch: as long as the file is in the directory, it gets picked up. To drop one, move its directory away; to just save context, `skills.maxIndexed` caps the index.'
1133
+ no_toggle: 'There is no "turn this skill off" switch: as long as the file is in the directory, it gets picked up. To drop one, use Delete on its row — that removes its directory for good; to just save context, `skills.maxIndexed` caps the index.'
1069
1134
  import_entry: Import a skill
1070
1135
  import_title: Import a skill
1071
1136
  import_lede: Import from the machine running the server. Accepts a folder containing SKILL.md, a two-level category/name skill tree, a .zip of either, or a single .md with frontmatter.
@@ -1096,13 +1161,42 @@ web:
1096
1161
  skill_view_no_count: Looking at this will not affect its score — that tally only counts times the model actually used it.
1097
1162
  skill_truncated: This SKILL.md is large ({bytes} bytes in total); below is only the first stretch of it. Open the file directly to read the whole thing.
1098
1163
  skill_bytes: '{bytes} bytes in total'
1164
+ skill_remove: Delete
1165
+ skill_remove_ask: Delete {name}? Its whole directory goes with it, and this cannot be undone.
1166
+ skill_remove_yes: Delete
1167
+ skill_cancel: Cancel
1168
+ skill_removed: 'Deleted {name}. This is the directory that was just removed:'
1099
1169
  plugins: Plugins
1100
- plug_lede: 'A plugin is a bundle of extensions: slash commands, identities, skills, hooks, deny rules, MCP servers. Pick one from a marketplace already added on this machine this page can only pick from that list; it cannot take a path and cannot upload a file.'
1170
+ plug_lede: 'A plugin is a bundle of extensions: slash commands, identities, skills, hooks, deny rules, MCP servers, plus scripts in any language. There are two ways in: pick one from a marketplace already added here, or point at a plugin directory on this machine (that tier only creates a symlink). Uploading a file is not one of them.'
1101
1171
  plug_installed_head: Installed ({count})
1102
1172
  plug_installed_empty: No plugins installed.
1103
1173
  plug_market_head: In the marketplaces ({count})
1104
- plug_market_empty: 'The marketplaces added here list no plugins. Adding a marketplace happens on the command line: epoch plugin marketplace add <address>'
1174
+ plug_market_empty: The marketplaces added here list no plugins they were added, their catalogues are just empty.
1175
+ plug_market_none_added: No plugin marketplace has been added yet. Use Add above, or point at a plugin directory on this machine to install one directly.
1105
1176
  plug_no_desc: (no description for this entry in the marketplace catalogue)
1177
+ plug_add: Add
1178
+ plug_add_title: Add a plugin
1179
+ plug_add_back: Back to the plugin list
1180
+ plug_add_tab_local: Local directory
1181
+ plug_add_tab_market: Marketplace
1182
+ plug_add_hint_local: 'This is a path on the machine running this service, not on your computer. It has to be absolute and point at a plugin directory — one holding an epoch-plugin.json. Installing it only creates a symlink: edit the source and restart once for the change to take effect.'
1183
+ plug_add_path_placeholder: /home/you/dev/my-toolkit
1184
+ plug_add_path_label: Absolute path of the plugin directory
1185
+ plug_add_look: See what it installs
1186
+ plug_add_browse: Browse…
1187
+ plug_add_browse_off: Collapse
1188
+ plug_add_pick_here: Pick this level
1189
+ plug_market_lede: A marketplace is a catalogue of plugins (one JSON file). Adding one installs nothing — once it is in, go back to the list and pick what to install there; the pre-install preview on that path is unchanged.
1190
+ plug_market_placeholder: /srv/market or github:acme/market
1191
+ plug_market_label: Marketplace address
1192
+ plug_market_add: Add marketplace
1193
+ plug_market_add_failed: That marketplace could not be added
1194
+ plug_market_none: No marketplace added.
1195
+ plug_market_entries: '{count} entries'
1196
+ plug_market_remove: Remove
1197
+ plug_market_remove_ask: Remove it? Plugins installed from it stay; you just will not be able to pick new ones here.
1198
+ plug_market_remove_yes: Remove
1199
+ plug_market_remove_failed: Could not remove it
1106
1200
  plug_restart: Plugins changed during this run; those changes have not taken effect
1107
1201
  plug_restart_why: 'Extensions are loaded at startup, so anything just installed / updated / uninstalled only counts from the next run — this run''s command table, hooks and identity table are unchanged. To make them take effect: restart the service running this interface (Ctrl+C in the terminal, then run epoch web again). If this interface is embedded in another program, only that program can do the rebuild — there is no "apply now" button on this page, and there will not be one.'
1108
1202
  plug_state_active: In effect
@@ -1297,6 +1391,9 @@ web:
1297
1391
  how_to_block: To block a tool, add a deny rule to the permission table. Rules only count once they are in a config file, and this interface never writes config — the security centre shows which rules exist today and which layer each came from.
1298
1392
  goto_rules: Open the rule table
1299
1393
  mcp_note: Tools from MCP servers are in this table too, named mcp__<server>__<tool>. The Source column says which server each one came from.
1394
+ tip:
1395
+ what: What this is
1396
+ about: About {name}
1300
1397
  about:
1301
1398
  loading: Reading…
1302
1399
  lede: When something goes wrong, paste a screenshot of this into the issue — it beats describing the symptoms.
@@ -1422,15 +1519,26 @@ web:
1422
1519
  remove_confirm: Delete "{name}"? Its registration in the OS scheduler goes with it. Run recordings are kept.
1423
1520
  form_new: New scheduled task
1424
1521
  form_edit: Edit scheduled task
1522
+ g_what: What it does
1523
+ g_what_note: The thing it runs when the time comes
1524
+ g_where: Where it runs
1525
+ g_where_note: Both optional — each has a definite default
1526
+ g_when: When it runs
1527
+ g_may: What it may do
1528
+ g_may_note: This group decides which confirmations it presses for you at night
1529
+ g_cost: How much it may spend
1530
+ g_cost_note: Three limits; hitting any one stops the run
1531
+ required: Required
1425
1532
  f_name: Name
1426
1533
  f_prompt: Prompt
1427
1534
  f_prompt_hint: What the agent should do when it fires. Say what it should leave behind — nobody will be there to ask.
1428
1535
  f_workdir: Workspace (optional)
1429
- f_workdir_hint: Absolute path on the server
1430
- f_workdir_note: Left empty, it runs in ~/.epoch/automation/<task id>/. It does **not** inherit any terminal's current directory.
1536
+ f_workdir_note: Left empty, it runs in ~/.epoch/automation/<task id>/. It never inherits any terminal's current directory.
1431
1537
  f_workdir_default: ~/.epoch/automation/<task id>/
1538
+ f_workdir_none: No specific workspace
1432
1539
  f_model: Model (optional)
1433
1540
  f_model_hint: Empty uses the configured one
1541
+ f_model_provider_note: Whichever provider you pick is saved with the task and used when it fires. If that provider has no key on this machine at fire time, the run falls back to the default model and records a line about it.
1434
1542
  f_kind: Frequency
1435
1543
  k_daily: Daily
1436
1544
  k_weekly: Weekly
@@ -1446,11 +1554,14 @@ web:
1446
1554
  f_days_note: The 31st does not fire in short months — that is the native behaviour on both platforms.
1447
1555
  f_start: Active from (optional)
1448
1556
  f_end: Active until (optional)
1557
+ f_window_note: Both ends are inclusive. Leave an end blank and that end is unbounded. Firings that fall outside the range are recorded as "skipped (outside active range)" and are not made up later.
1558
+ f_window_once: A run-once task cannot carry an active range — it already is a single date, so these two cannot be filled in on this kind.
1449
1559
  f_permission: Permission level
1450
1560
  f_bypass_ack: 'I understand: this task will do anything without asking inside {workDir}, including deleting files and running arbitrary commands.'
1451
1561
  f_turns: Max turns
1562
+ f_turns_hint: How many times the model may call a tool in one turn, 1 to 1000. On hitting the cap it stops and finishes its answer.
1452
1563
  f_timeout: Timeout (minutes)
1453
- f_timeout_note: The only ruler that catches "a command hung" — turns and spend both stop growing there.
1564
+ f_timeout_note: The only ruler that catches "a command hung" — turns and spend both stop growing there. 24 hours at most.
1454
1565
  f_budget: Max spend per run (USD)
1455
1566
  f_budget_why: 'Required, and we give no default: nobody is watching this task, and a fake default would make "woke up to $40" our fault.'
1456
1567
  save_and_run: Save and run once
@@ -1800,6 +1911,8 @@ skill:
1800
1911
  import_bad_name_dots: A skill name cannot be . or ..
1801
1912
  import_bad_name_char: 'The skill name "{name}" contains characters that cannot be used in a directory name (< > : " / \ | ? * and control characters). The colon in particular separates the plugin/host prefix — a user-level skill taking it would masquerade as a plugin skill'
1802
1913
  import_bad_name_trailing: The skill name "{name}" ends with a dot or a space — on Windows such a directory can be created but not opened
1914
+ remove_no_system: The skill system did not start this run — no skills are loaded in this process, so there is nothing to delete. Check the Skill entries in the startup diagnostics
1915
+ remove_unknown: There is no skill named "{name}" — the list you are looking at may be stale; refresh it
1803
1916
  host_capabilities:
1804
1917
  loaded: '{namespace}: {roles} identity/identities, {skillDirs} skill directory/directories, {mcpServers} MCP server(s), {tools} tool(s) (the first two get the {namespace}: prefix, the last two get {namespace}__ — both end up inside a tool name, and tool names cannot contain a colon)'
1805
1918
  bad_role_name: Identity "{name}" has an invalid name (lowercase letters, digits and hyphens only, and it may not start with a hyphen); that one was skipped, the rest were injected as usual
@@ -1934,6 +2047,7 @@ mcp_write:
1934
2047
  invalid: the configuration is not valid
1935
2048
  mcp_config:
1936
2049
  invalid_json: 'Nothing was written — this content is not valid JSON: {why}'
2050
+ stale_disk: nothing was written — ~/.epoch/mcp.json changed while this edit was being computed; read it again and redo the change
1937
2051
  role_write:
1938
2052
  bad_name: The identity name "{name}" cannot be used — lowercase letters, digits and hyphens only, and it may not start with a hyphen. This name is both the file name and the word used to delegate work (the `role` argument of delegate_task, the value of --agent), which is why it is validated strictly.
1939
2053
  taken: 'There is already an identity named "{name}" — pick another name. Nothing is overwritten and nothing is auto-renamed here: overwriting would delete the prose you wrote in that file, and renaming would make the word used to delegate work disagree with what you typed.'
@@ -1956,7 +2070,11 @@ plugin_control:
1956
2070
  market_remote_off: 'Host-preset marketplace {source} is a remote address (fetching its catalog makes an HTTP request) and the remote tier is off by default, so it was skipped; local directories are unaffected. To allow it, pass allowRemote: true in hostMarketplaces to buildRuntime'
1957
2071
  market_failed: 'Host-preset marketplace {source} did not come up: {reason}. It was skipped, the rest are unaffected'
1958
2072
  market_ready: Marketplace {name} preset ({count} plugins available)
1959
- not_a_ref: '"{ref}" is not a marketplace reference (the shape is <marketplace>/<plugin>; epoch plugin search lists what is there). This path **can only pick from the marketplaces already added** — it cannot take an arbitrary path. To install something outside those catalogs, use epoch plugin install on the command line'
2073
+ not_a_ref: '"{ref}" is not a marketplace reference (the shape is <marketplace>/<plugin>; epoch plugin search lists what is there). This path **can only pick from the marketplaces already added** — to install a directory on this machine use the local directory route instead, and for remote things outside those catalogs use epoch plugin install on the command line'
2074
+ not_local_path: '"{path}" is not a local absolute path. This route only takes a plugin directory on this machine (installing it just creates a symlink); it does not take github: / npm: / archive URLs — those come from a marketplace, or from epoch plugin install on the command line. Relative paths are refused too: they would resolve against the working directory of the process running this server, which is rarely where you think it is'
2075
+ market_source_empty: 'The marketplace source is empty. Give a local path or directory, or a github: / https:// address'
2076
+ market_add_remote_off: '{source} is a remote marketplace address (fetching its catalog makes an HTTP request) and the remote tier is off by default. Local paths are unaffected; to allow remote, pass allowRemote: true in hostMarketplaces to buildRuntime'
2077
+ market_missing: Marketplace {name} was never added
1960
2078
  entry_remote_off: 'The source of {ref} is {source}; installing it would git clone / download and unpack, and the remote tier is off by default (the local tier only creates a symlink — not the same order of risk). To allow it, pass allowRemote: true in hostMarketplaces to buildRuntime'
1961
2079
  update_remote_off: '{name} was installed from {source}; updating it would git clone / download again, and the remote tier is off by default. To allow it, pass allowRemote: true in hostMarketplaces to buildRuntime'
1962
2080
  linked_no_update: '{name} is a local symlink ({source}) — edit the source directory and rebuild the runtime; there is nothing to update'
@@ -2073,6 +2191,7 @@ provider:
2073
2191
  label_openai_compatible: OpenAI Compatible (generic adapter)
2074
2192
  response_format_with_tools: 'A single request cannot ask for both tools and responseFormat: the model either calls tools or emits one JSON document, pick one'
2075
2193
  response_format_dropped: '{model} did not honour this round''s forced-JSON constraint (the request was still sent), so this round''s output may not be JSON'
2194
+ key_not_applicable: '{provider} does not use an API key — one stored here would never be read'
2076
2195
  utility:
2077
2196
  aborted: This one-shot completion was aborted before it was sent (not sent, not billed)
2078
2197
  terminal_setup:
@@ -3125,6 +3244,7 @@ untrusted:
3125
3244
  skipped: The workspace is not trusted, so project {what} were not loaded (epoch trust can trust this directory)
3126
3245
  what_commands: commands
3127
3246
  what_policy: policy rules
3247
+ what_compliance: compliance lexicons
3128
3248
  what_skills: skills
3129
3249
  what_roles: roles
3130
3250
  hooks_tail: The workspace is not trusted, so project hooks were not loaded (epoch trust can trust this directory) — hooks run commands, and there is no "the narrowing part still applies" exception here
@@ -3170,6 +3290,7 @@ hook_load:
3170
3290
  bad_event: 'Not a valid hook event name; ignored. Available: {types}'
3171
3291
  not_array: Wanted an array (one hook per element); the whole event was ignored
3172
3292
  entry_skipped: '{message}; this hook was skipped'
3293
+ no_plugin_root: This uses ${EPOCH_PLUGIN_ROOT}, but the config did not come from a plugin, so we have no plugin root to give — the placeholder is left verbatim and the path most likely points nowhere
3173
3294
  rejected: A hook rejected the operation
3174
3295
  rejected_this: A hook rejected this operation
3175
3296
  timeout: The command hook timed out
@@ -3181,6 +3302,40 @@ policy_load:
3181
3302
  not_kv: 'Neither [[rule]] nor key = "value"; ignored: {preview}'
3182
3303
  before_first_rule: '{key} appears before the first [[rule]]; ignored'
3183
3304
  rule_skipped: '{message}; this rule was skipped'
3305
+ compliance_cmd:
3306
+ cmd_root: Self-check for the content compliance gate
3307
+ cmd_check: Check whether a piece of text would be blocked, and which characters matched
3308
+ cmd_status: Show the current state on this machine (switches, rule counts, per-category action)
3309
+ arg_text: the text to check
3310
+ opt_stdin: read from standard input (useful for checking a whole file)
3311
+ need_text: Check what? Pass some text, or add --stdin to read from standard input
3312
+ disabled: 'Content compliance is not enabled (compliance.enabled: false), so nothing is checked'
3313
+ clean: 'Clean: nothing matched'
3314
+ verdict: 'Verdict: {action}, {count} matches'
3315
+ hit: ' [{category}] {action} <- {excerpt} (rule {rule})'
3316
+ blocked: 'This is the block tier: on the input side the request is never sent (and never billed); with the output side enabled, the sentence that matched never reaches the screen'
3317
+ released: 'After filtering: {text}'
3318
+ status_enabled: 'Enabled: {enabled}'
3319
+ status_rules: 'Rules: {active} active ({builtin} built-in + {user} from your lexicons)'
3320
+ status_dir: 'User lexicon directory: {dir}'
3321
+ status_scope: 'Channels: output {output} / input {input} / skip code {code}'
3322
+ status_action: ' {category} -> {action}'
3323
+ status_project_note: 'Note: project-level lexicons (<project root>/.epoch/compliance/) go through the workspace trust gate, and this command does not read them — a real session does'
3324
+ compliance:
3325
+ category_sep: ', '
3326
+ category:
3327
+ sexual: sexual content
3328
+ political: political / national security
3329
+ hate: ethnic or racial discrimination
3330
+ violence: violence and terrorism
3331
+ illegal: illegal activity
3332
+ self_harm: self-harm
3333
+ privacy: personal sensitive data
3334
+ compliance_load:
3335
+ dir_read_failed: 'Could not read the compliance lexicon directory: {reason}'
3336
+ file_read_failed: 'Could not read it, so the whole file was skipped: {reason}'
3337
+ unknown_file: 'The file name is not a category name, so the whole file was skipped. Known categories: {categories} (regexes go in <category>.regex.txt, exemptions in exempt.txt)'
3338
+ bad_regex: 'The regex did not compile, so this entry was skipped: {reason}'
3184
3339
  skill_load:
3185
3340
  degraded: '{message}; loaded as if it had no frontmatter'
3186
3341
  not_a_map: The frontmatter is not key/value pairs (the top level has to be a YAML object); loaded as if it had no frontmatter
@@ -3283,7 +3438,12 @@ context_breakdown:
3283
3438
  agent_run:
3284
3439
  error: 'Error: {message}'
3285
3440
  images_unsupported: The current model does not support image input, so this request was cancelled (not sent, not billed). Switch to a vision-capable model (gpt-4o / claude-sonnet-4-6 / gemini-2.5-flash), or drop the {count} images and retry.
3441
+ images_dropped: '{provider} dropped the {count} images in this turn (its own words: {feature}), so the model never saw them — do not trust this turn''s answer. The request was sent and billed. Most likely this provider''s SDK version cannot carry images yet, or this model does not actually accept them.'
3442
+ images_dropped_provider_unknown: The current provider
3286
3443
  content_filter: The model refused to respond for safety reasons; try asking a different way.
3444
+ compliance_output: This reply ran into a content compliance limit ({categories}), so output stopped.
3445
+ compliance_input: Your message ran into a content compliance limit ({categories}); the request was not sent and nothing was billed.
3446
+ compliance_input_masked: Your message contained wording that runs into a content compliance limit ({categories}), so it was masked before being sent — the model did not see the text exactly as it appears on your screen.
3287
3447
  stalled: 'Detected a loop: {info}. The task was terminated.'
3288
3448
  stall_info: '{tool}({args}) was called {count} turns in a row, which looks like a loop'
3289
3449
  max_turns: Reached the maximum of {max} turns; the task was terminated unfinished.
@@ -3472,6 +3632,7 @@ secret:
3472
3632
  infra_misc:
3473
3633
  shell_not_on_path: 'shell: the configured {kind} ({exe}) is not on PATH; fell back to cmd.exe'
3474
3634
  ripgrep_install_windows: winget install BurntSushi.ripgrep.MSVC (or scoop install ripgrep)
3635
+ ripgrep_chmod_hint: chmod +x "{path}" — prefix with sudo if that directory is root-owned
3475
3636
  schema_failed: '{path} failed validation ({count} problems):\n{detail}'
3476
3637
  schema_more: '{count} more problems not listed'
3477
3638
  schema_unknown_key: Unknown setting, ignored (other settings still apply)
@@ -3517,6 +3678,9 @@ boot:
3517
3678
  managed_hooks_only: Managed enterprise settings turned on allowManagedHooksOnly, so neither user-level nor project-level hooks load ({path})
3518
3679
  policy_load_failed: loading failed (skipped)
3519
3680
  perm_rules: '{total} permission rules (allow {allow} / ask {ask} / deny {deny})'
3681
+ compliance_ready: '{active} rules active ({builtin} built-in + {external} from lexicon files)'
3682
+ compliance_off: 'not enabled (compliance.enabled: false)'
3683
+ compliance_load_failed: the external lexicons could not be loaded, so only the built-in seed list is in use
3520
3684
  commands_all_failed: None of the {count} files could be loaded, so there are no usable custom commands
3521
3685
  command_allowed_tools: '{tools} in the allowed-tools of {name} do not exist and were ignored'
3522
3686
  tools_cost: '{count} tools ({split}) ≈ {tokens}k tokens/turn'
@@ -3526,6 +3690,7 @@ boot:
3526
3690
  shell_kind: '{kind} (terminal tool commands are handed to it)'
3527
3691
  plugin_register_failed: registration failed ({reason})
3528
3692
  ripgrep_missing: not installed, so file_search is unavailable ({hint})
3693
+ ripgrep_not_executable: shipped but not executable, so file_search is unavailable for now ({hint})
3529
3694
  ripgrep_system: using the system rg
3530
3695
  ripgrep_builtin: using the bundled rg{version}
3531
3696
  mcp_none: not configured (~/.epoch/mcp.json is missing or empty)