kira-wallet 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -43,11 +43,12 @@ For a viewer without wallet or research actions, use `kira start --read-only`.
43
43
  Prefer an archive? [GitHub releases](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2)
44
44
  include the same package and its checksum.
45
45
 
46
- 1. **Add a watching wallet.** Paste a public address and give it a name. Add more
47
- wallets whenever you need them.
48
- 2. **Set up discovery.** In **Workspace settings**, connect an indexer for broad
46
+ 1. **Set up discovery.** In **Workspace settings**, connect an indexer for broad
49
47
  ERC20 discovery. Public RPC alone has limited discovery; incomplete coverage
50
- stays visible.
48
+ stays visible. An AI or browser wallet connection does not enable an indexer.
49
+ 2. **Add a watching wallet.** Paste a public address and give it a name. Add more
50
+ wallets whenever you need them. Without discovery, the form explicitly
51
+ offers limited research of native and Mint Club assets.
51
52
  3. **Connect your AI.** Install and sign in to the
52
53
  [Codex CLI](https://developers.openai.com/codex/cli) or
53
54
  [Claude Code](https://code.claude.com/docs/en/quickstart), then choose your
@@ -117,6 +118,12 @@ package. Use `--data-dir /absolute/private/directory` before a command to choose
117
118
  another workspace. For connection, model or provider help, see the
118
119
  [CLI guide](docs/CLI.md) and [viewer guide](viewer/README.md).
119
120
 
121
+ From 0.1.4, a partial wallet analysis explains disabled discovery, missing
122
+ credentials and network failures directly beside the holdings. After connecting
123
+ discovery, use **Refresh holdings** to create a new analysis. **Refresh prices**
124
+ does not discover missing tokens. Existing workspace provider settings and
125
+ published snapshots are preserved.
126
+
120
127
  ## Build and contribute
121
128
 
122
129
  ```sh
package/docs/CLI.md CHANGED
@@ -10,8 +10,9 @@ npm install -g kira-wallet
10
10
  kira setup
11
11
  ```
12
12
 
13
- The npm registry currently offers version 0.1.2. The same archive and checksum
14
- are available from the [GitHub release](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2).
13
+ The operator published npm version 0.1.4. Version 0.1.5 is a preparation candidate. GitHub v0.1.3 remains
14
+ a draft; the previous archive and checksum are available from the
15
+ [0.1.2 GitHub release](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2).
15
16
  Registry publication remains an operator action. The code and original character assets use the MIT license.
16
17
 
17
18
  ## Lifecycle
@@ -33,7 +34,7 @@ read-only and control modes requires an explicit stop and restart. Starting
33
34
  an already-running app in the same mode reuses its URL. The old `--controls`
34
35
  option remains accepted for existing scripts but is no longer needed.
35
36
 
36
- These startup defaults apply from 0.1.3. In the published 0.1.2 package, use
37
+ These startup defaults apply from published 0.1.3. In the older 0.1.2 package, use
37
38
  `kira setup` or `kira start --controls` for the full app.
38
39
 
39
40
  The 0.1.2 GitHub release starts with a wallet checklist. Add a public EVM
@@ -41,17 +42,22 @@ address or use the browser wallet picker. Registration starts one explicit
41
42
  research job; follow it in Activity and read the saved portfolio report.
42
43
  No signature, seed phrase or private key is needed.
43
44
 
45
+ Published 0.1.4 explains limited discovery before wallet registration and beside
46
+ holdings. Missing coverage keeps the portfolio estimate partial. Existing snapshots require a holdings refresh after connecting
47
+ an indexer; a price refresh does not discover missing tokens.
48
+
44
49
  Public RPC checks known assets and Mint Club. For broader ERC20 discovery,
45
- open Research connections, select Alchemy and name the private environment
46
- variable holding its key. Never paste the key into the browser. `kira doctor`
50
+ open Data provider connection, select Alchemy and follow its local key guide.
51
+ The 0.1.5 candidate uses one save for Alchemy RPC and discovery; chain overrides
52
+ remain under Advanced. Never paste the key into the browser. `kira doctor`
47
53
  checks whether the configured key is available. Configuration alone does not
48
54
  prove complete coverage; inspect the recorded results and gaps.
49
55
 
50
56
  Connecting Codex or Claude is optional. Use Connect your AI when ready, verify
51
57
  a generic response and explicitly choose context permissions. Assistant answers
52
58
  render a safe Markdown subset; raw HTML, images and links remain inert text.
53
- Install the 0.1.1 GitHub archive for this wallet-first setup. The npm registry's
54
- 0.1.0 package continues to use the earlier account-first setup.
59
+ The wallet-first setup is included in published npm 0.1.3. The historical
60
+ 0.1.0 package uses the earlier account-first setup.
55
61
 
56
62
  ## Your model account
57
63
 
@@ -62,8 +68,12 @@ authentication. Verified adapter versions are Codex 0.158.x and Claude Code
62
68
  verified. This is a native CLI connection, with no Kira account, API-key proxy or
63
69
  hosted portfolio backend.
64
70
 
65
- In setup, choose the CLI account and an optional model ID. The blank model field
66
- uses the CLI default. Choose no automatic wallet context, one registered wallet,
71
+ In the 0.1.5 candidate, setup opens the existing Account / Permissions / Ready
72
+ modal directly. Choose Codex or Claude, then Use default model or a readable
73
+ model choice. Codex choices come from bounded native metadata; Claude offers
74
+ Sonnet, Opus and Haiku aliases. A catalog is not proof of account access.
75
+ Advanced accepts a custom identifier and preserves saved identifiers absent
76
+ from the catalog. Workspace settings reopens the same selector and permissions. Choose no automatic wallet context, one registered wallet,
67
77
  or the whole recorded portfolio. A generic response check sends no wallet data
68
78
  and must pass before these settings are saved.
69
79
 
@@ -73,7 +83,10 @@ and contract identities, timestamps, unknown prices and coverage. Credentials,
73
83
  RPC endpoints, raw provider errors and local paths are excluded. Changing the
74
84
  provider, model, context or selected wallet creates a new conversation.
75
85
 
76
- Kira-local history is opt-in and uses private `conversations/` files. With history
86
+ New setup in 0.1.5 recommends saving history and preselects it. Existing saved
87
+ true and false choices are restored exactly. Saving lets you reopen chats after
88
+ restart; it does not expand model memory or wallet access. The choice and
89
+ research permission remain editable in Workspace settings. Kira-local history uses private `conversations/` files. With history
77
90
  off, conversations and drafts stay in memory. A new server process starts a fresh
78
91
  conversation; saved files remain available locally and are not automatically
79
92
  replayed. Native CLI, administrator policy and provider data policies remain a
@@ -129,8 +142,8 @@ kira config import-env --file /absolute/private/rpc.env --key-env ALCHEMY_API_KE
129
142
  ```
130
143
 
131
144
  The import references the file instead of copying keys. It configures Alchemy
132
- RPC and portfolio discovery separately, with public fallback enabled to match
133
- the historical engine. Environment overrides the file. Missing files or keys
145
+ RPC and portfolio discovery together, preserving existing custom chain overrides
146
+ and explicit fallback preferences. New configurations allow public fallback. Environment overrides the file. Missing files or keys
134
147
  produce limited coverage; malformed configuration fails with safe errors.
135
148
  The import does not contact providers. Private settings are never served.
136
149
 
@@ -8,7 +8,8 @@ this contract. Chat interruption never cancels an engine job.
8
8
  Mutations use `{schema_version: 1, operation, input, idempotency_key}`.
9
9
  Supported operations are `wallet.add` (address, exact tag), `wallet.refresh`
10
10
  (wallet address or exact tag), `prices.refresh` (same), `wallet.setTags`
11
- (wallet, exact tags), `job.resume` and `job.cancel` (job_id).
11
+ (wallet, exact tags), `settings.rpc`, `settings.discovery`, `settings.provider`,
12
+ `job.resume` and `job.cancel` (job_id).
12
13
  Read operations are `job.read`, `job.list`, `snapshot.read`, and
13
14
  `snapshots.compare`. The portfolio projection retains its existing schema.
14
15
 
@@ -21,6 +22,17 @@ the same job without rerunning providers. Resume requeues the same job ID and
21
22
  increments its attempt on execution. Retain the key until the job is removed
22
23
  by a future explicit retention policy. Version mismatches fail closed.
23
24
 
25
+ `settings.provider` accepts exactly `{provider: "public" | "alchemy", key_env}`.
26
+ The key reference is a bounded environment variable name, never a credential.
27
+ One writer-locked atomic replacement changes RPC mode and discovery together.
28
+ Alchemy updates generated network references while preserving explicit custom
29
+ chain overrides, unrelated chains, explorer choice and public fallback policy.
30
+ A missing local key fails before replacement. Public mode keeps inactive custom
31
+ references for later use. A detected key is not a validated provider or complete
32
+ holdings result. Existing settings.rpc and settings.discovery requests remain
33
+ compatible. The browser waits for terminal settings results; retrying a lost
34
+ result checks the same accepted job instead of submitting another change.
35
+
24
36
  ## Envelope
25
37
 
26
38
  Each private `jobs/<uuid>.json` records schema_version, job_id, operation,
@@ -0,0 +1,95 @@
1
+ # Token discovery patch acceptance
2
+
3
+ Version: 0.1.4 candidate. Published npm 0.1.3 remains immutable.
4
+
5
+ ## Problem and correction
6
+
7
+ A new personal data directory defaults to public RPC and no general ERC20
8
+ indexer. The installed app does not inherit discovery settings from a separate
9
+ research workspace. Public RPC reads native balances and enumerates Mint Club
10
+ assets, but cannot identify arbitrary ERC20 contracts for a wallet. Previously,
11
+ a finished analysis could show only native holdings without explaining this
12
+ configuration at the point of use.
13
+
14
+ The wallet registration dialog now explains limited discovery and offers a
15
+ connection action before research. The wallet overview explains disabled
16
+ discovery, missing credentials, stale analysis or incomplete provider checks.
17
+ It labels incomplete portfolio estimates as partial. Network details retain
18
+ separate discovery, RPC and registry coverage. Read-only and sample views do
19
+ not acquire research permissions.
20
+
21
+ RPC and candidate-balance failures now receive an explicit warning with
22
+ expanded affected-network details. Retry holdings starts the existing gated
23
+ research action; Review RPC settings opens the existing connection dialog.
24
+ The warning says to wait briefly before retrying and review the connection
25
+ or choose another provider if failures persist. Missing balances remain
26
+ unknown. Successful discovery alone cannot mark failed balance reads complete,
27
+ and old snapshots can project their fixed balance-failure note safely.
28
+
29
+ Connecting an indexer does not change an immutable saved analysis. Refresh
30
+ holdings runs discovery again. Refresh prices only updates existing holdings.
31
+ Discovery settings preserve existing explorer preferences, and the UI rereads
32
+ readiness after the settings job succeeds.
33
+
34
+ Registry diagnostics now retain up to five redacted error examples and numeric
35
+ HTTP or RPC codes in local evidence. The public model exposes only status and
36
+ counts. Oversized multicalls split at the same observed block, including errors
37
+ that viem converts into failed result rows. Transport outages and throttling
38
+ remain bounded instead of retrying every contract separately.
39
+
40
+ ## Verification
41
+
42
+ - Required `npm test` suite passed: 152 Python tests, required Node behavior
43
+ checks and browser script syntax checks.
44
+ - `scripts/check-install.py` passed with a fresh 73-member package installation
45
+ and startup/shutdown lifecycle in a temporary synthetic workspace.
46
+ - The tracked-source privacy scan passed with the active private data directory
47
+ included. The full-history scan retains the previously recorded non-noreply
48
+ merge identity at `2ed52a2`; no new finding was introduced. That historical
49
+ identity is not reproduced here and public history was not rewritten.
50
+ - Actual viem with an in-memory aggregate3 transport: eight calls rejected
51
+ above two calls per batch succeed in seven requests, all at block 103.
52
+ Independent review also checked that HTTP 429 and 503 stay at one request
53
+ and isolated contract failures remain isolated. No live RPC is used by this
54
+ regression.
55
+ - Missing, unreadable and disabled discovery credentials produce explicit
56
+ coverage status without exposing credentials or attempting discovery.
57
+ - Viewer projection keeps raw provider diagnostics private and preserves
58
+ unknown prices. UI behavior checks cover disabled discovery, missing
59
+ credentials, stale analysis after connection, read-only actions and
60
+ partially successful explorer coverage.
61
+ - RPC recovery regressions cover unavailable chains, registry failures,
62
+ non-Mint candidate-balance failures, mixed missing connections, unknown
63
+ valuations, read-only controls and removal after a successful analysis.
64
+ - Chrome fixture checks at 1440 x 900 and 390 x 844: partial estimate and
65
+ discovery notice render without horizontal overflow. Both connection actions
66
+ open Research connections. The add-wallet dialog says Add with limited
67
+ discovery when the provider is absent. Fixture actions submitted no research,
68
+ account authorization or provider requests.
69
+ - RPC-failure fixtures at the same desktop/mobile sizes show expanded Base
70
+ and BNB Chain details, both recovery actions and a partial estimate without
71
+ horizontal overflow. Review RPC settings opens the connection dialog.
72
+
73
+ The design references were Mixpanel's dashboard connection guidance and
74
+ Databricks' inline data-check failure state, retrieved
75
+ with [Lazyweb](https://www.lazyweb.com/agentic-search/51280761-491a-4a3f-a78e-ed2dcd427ee8).
76
+ No operator wallet information or screenshots were submitted to that service.
77
+
78
+ Independent review found a viem failed-result handling defect during
79
+ implementation. The correction and actual-client regression were reviewed
80
+ again and the finding was closed. Screenshots and operator test evidence
81
+ remain in the ignored local ticket ledger.
82
+
83
+ ## Acceptance limits and release boundary
84
+
85
+ Operator-approved reuse of an existing discovery connection recovered
86
+ non-native holdings in a new saved analysis. Some provider and network gaps
87
+ remain; this does not establish complete holdings or verify every expected
88
+ token. Those limits remain visible in coverage. The existing operator server
89
+ was not restarted or globally upgraded during patch verification.
90
+
91
+ The source/package privacy scan is required before preparation.
92
+ `scripts/prepare-release.py`
93
+ pins the clean source commit and archive checksum. Registry publication
94
+ remains operator-only. No npm publication, GitHub release publication or PR
95
+ merge is part of preparing this candidate.
@@ -4,6 +4,27 @@ Kira connects the user's installed, unmodified Codex or Claude Code CLI. It does
4
4
 
5
5
  The setup flow separates account readiness, model choice, context permission, and confirmation. The conversation composer remains visible. It shows the connected CLI, automatic context scope, response state, cancellation, and a new conversation action. Enter sends; Shift+Enter adds a line; composition events never send.
6
6
 
7
+ `kira setup` opens the existing Account, Permissions and Ready modal directly
8
+ after the local session and workspace are available. Opening setup does not run
9
+ a model response check, save permissions or start wallet research. Existing
10
+ settings are loaded when setup is reopened. Closing the modal consumes that
11
+ page's setup request, so background polling does not reopen it. Ordinary
12
+ `kira start` keeps the normal workspace entry behavior.
13
+
14
+ The 0.1.5 selector defaults to Use default model. Codex uses a bounded stdio
15
+ app-server handshake and model/list request; it never starts a thread or turn.
16
+ The subprocess has an eight-second metadata deadline and 256 KB output bound.
17
+ Metadata failures leave the default and Advanced identifier entry available.
18
+ Only installed supported CLI versions are queried. Claude uses its documented
19
+ Sonnet, Opus and Haiku aliases. Every chosen model still requires a deliberate
20
+ generic response test. Vendor account and managed policies may restrict access.
21
+ Workspace settings reuses this modal for both providers and all permissions.
22
+ Fresh setup preselects local history; existing false values remain false.
23
+ History permits reopening chats, without extending model memory or scope.
24
+ No wallet context permits general chat and manual wallet management while
25
+ wallet context and research tools remain unavailable. Any effective model or
26
+ permission change starts a fresh conversation.
27
+
7
28
  Automatic context is none, one explicitly selected registered wallet, or the whole recorded portfolio. An allowlisted projection preserves exact balances, contract and chain identities, timestamps, unknown values, and coverage gaps. It excludes credentials, RPC endpoints, paths, raw errors, jobs, and provider payloads. User-entered text is also sent to the selected provider. Scope, wallet, provider, or model changes start a new conversation and never replay old messages. Only a bounded current conversation is replayed.
8
29
 
9
30
  Approved wallet context can include validated historical full-balance Mint Club
@@ -53,7 +74,7 @@ in Activity and also appear in the chat tray. Unanalysed wallets show a sidebar
53
74
  spinner and open Activity while their first holdings job is active.
54
75
 
55
76
  History is capped at 100 memory sessions and reads up to 200 validated local
56
- files. Saving remains opt-in. Memory sessions disappear when the process stops.
77
+ files. Saving is recommended for fresh setup and remains an editable choice. Memory sessions disappear when the process stops.
57
78
  A previous session can continue only with exactly the same model and context
58
79
  configuration. Reading history does not expand model access. Old saved files
59
80
  remain readable. No missing memory-only conversations are reconstructed.
@@ -65,3 +86,15 @@ The receipt is consumed only on success. This does not enable history retention.
65
86
  The Kira system voice is concise, composed and practical. It leads with findings
66
87
  and preserves evidence, unknown values and signing boundaries. Personality is
67
88
  prompt guidance; response quality still depends on the selected model.
89
+
90
+ ## 0.1.5 preparation evidence
91
+
92
+ The combined settings and model-selection tests cover catalog failures, stale
93
+ responses, custom identifiers, fresh retention defaults, saved opt-outs,
94
+ atomic provider changes, preference preservation, missing keys, cancellation
95
+ and ambiguous receipts. Desktop 1440 × 900 and mobile 390 × 844 checks used an
96
+ empty disposable workspace. Codex 0.158.0 and Claude Code 2.1.293 completed
97
+ native default-model response checks with no wallet data. The provider save
98
+ used a synthetic local key reference and made no provider research request.
99
+ Real extension approval, personal OWS passphrases and live holdings coverage
100
+ are separate operator acceptance. Published 0.1.4 snapshots are not rewritten.
package/docs/RELEASE.md CHANGED
@@ -5,7 +5,9 @@
5
5
  GitHub version releases and npm registry publication are separate. The GitHub
6
6
  0.1.2 release provides the reviewed `kira-wallet-0.1.2.tgz`, its SHA-256 file and
7
7
  release manifest. Its tag pins the source used to create those assets. Install
8
- that archive from GitHub or use `npm install -g kira-wallet`; npm also serves 0.1.2.
8
+ that archive from GitHub. The operator published npm 0.1.3 on 2026-10-06;
9
+ its archive and installed bytes were verified separately. GitHub v0.1.3
10
+ remains a draft. GitHub and registry status must not be inferred from each other.
9
11
  Version 0.1.2 was published to GitHub on 2026-10-05 from
10
12
  `f994e99acb4e2d778fb4c95479f04355c88c00b3`. Its archive SHA-256 is
11
13
  `866b7f1ad543bff1ff59d3bf840eca6313eb6e206e78b8611186e5a971153f28`.
@@ -21,7 +23,7 @@ their invented wallet addresses, balances and seeded chat are documented in
21
23
 
22
24
  ## npm publication preparation
23
25
 
24
- The next candidate is **0.1.3**, which makes `kira start` open the full local
26
+ Published **0.1.3** makes `kira start` open the full local
25
27
  app by default. `--read-only` preserves the viewer-only mode, `--no-open`
26
28
  supports terminal-only use, and `--controls` remains compatible with existing
27
29
  scripts. A running instance never changes modes silently. Local session,
@@ -30,9 +32,31 @@ creation now identifies Open Wallet Standard with its official locally served
30
32
  logo and explains encrypted local storage and public-account research. The
31
33
  OWS integration and wallet-operation permissions are unchanged.
32
34
 
33
- This candidate requires PR review and operator publication. It is not yet
34
- available from the registry. The showcase HTML animation is deployed separately
35
- and requires no npm release. Keep the published 0.1.2 archive and tag immutable.
35
+ Published **0.1.4** makes incomplete token discovery visible
36
+ before registration and beside wallet holdings, separates disabled discovery
37
+ from missing credentials and provider failures, and offers a connection or
38
+ holdings-refresh action. Registry failures retain bounded redacted examples
39
+ and RPC error codes. Oversized multicalls split into smaller batches while
40
+ transport outages remain bounded. Existing explorer preferences are preserved
41
+ when discovery settings are saved. Connecting an indexer and refreshing
42
+ holdings are still required to recover tokens absent from an older snapshot.
43
+ RPC failures show affected networks, retry guidance and a settings action.
44
+ Failed candidate balance reads keep the saved result and valuation partial
45
+ even when general token discovery succeeded.
46
+
47
+ The next candidate is **0.1.5**. It opens the existing setup modal directly,
48
+ adds readable Codex and Claude model choices, and exposes model and permission
49
+ changes in Workspace settings. New setup recommends local history; upgrades
50
+ preserve explicit opt-outs. Watching wallet methods use individual buttons.
51
+ Data provider connection offers one atomic Alchemy RPC and discovery save,
52
+ with custom overrides under Advanced. A missing key keeps prior settings.
53
+ The wallet draft survives a connection detour. Connecting still requires an
54
+ explicit holdings refresh before missing assets can appear in saved evidence.
55
+
56
+ Version 0.1.5 requires independent review and operator registry publication.
57
+ It is not yet available from the registry. Keep published 0.1.4 and earlier
58
+ package bytes immutable. The showcase remains a separate deployment.
59
+ See [discovery acceptance](KIRA_DISCOVERY_ACCEPTANCE.md).
36
60
 
37
61
  The operator published npm **0.1.2** on 2026-10-05. Its registry archive was
38
62
  downloaded and matched the reviewed GitHub archive above, including SHA-256,
@@ -54,8 +78,7 @@ The earlier unpublished 0.1.2 candidate is preserved in the ignored
54
78
  `dist/preserved-0.1.2-bad8f02/` directory. Use the current `dist/release.json`
55
79
  and its matching archive for publication; do not mix manifests from candidates.
56
80
 
57
- Keep GitHub v0.1.1, its tag and its released archive immutable. Version 0.1.2
58
- is also released and immutable. Do not rebuild it from later documentation
81
+ Keep GitHub v0.1.1, its tag and its released archive immutable. Versions 0.1.2, 0.1.3 and 0.1.4 are also published and immutable. Do not rebuild them from later documentation
59
82
  commits. Future package changes need a new version. Release preparation does not
60
83
  publish a GitHub release, merge PR 19, promote the website or publish to npm.
61
84
  Registry publication remains operator-only. The coding agent never publishes.
@@ -70,12 +93,12 @@ python3 scripts/prepare-release.py
70
93
  ```
71
94
 
72
95
  `prepare-release.py` scans the package boundary and creates
73
- `dist/kira-wallet-0.1.3.tgz`. The ignored `dist/release.json` records its source
96
+ `dist/kira-wallet-0.1.5.tgz`. The ignored `dist/release.json` records its source
74
97
  commit, SHA-256 and member count. It never publishes. After reviewing that
75
98
  manifest, the operator's final command from the project root is:
76
99
 
77
100
  ```sh
78
- npm publish ./dist/kira-wallet-0.1.3.tgz --access public
101
+ npm publish ./dist/kira-wallet-0.1.5.tgz --access public
79
102
  ```
80
103
 
81
104
  Use the operator's npm account and complete authentication or OTP personally.
package/kira_agent.py CHANGED
@@ -251,6 +251,7 @@ class AgentStore:
251
251
  self.root = Path(root);self.path = self.root/'.kira-agent.json';self.runner=runner;self.detector=detector
252
252
  self.lock=threading.RLock();self.turns={};self.active=None;self.epoch=0;self.messages=[];self.conversation=str(uuid.uuid4());self.tested=set();self.config=None;self.workers=[];self.tool_status=None
253
253
  self._capabilities=None;self._checked=0
254
+ self._model_lock=threading.Lock();self._model_cache={}
254
255
  self.sessions={};self.started_at=datetime.now(timezone.utc).isoformat();self.updated_at=self.started_at
255
256
  if self.path.exists():
256
257
  try: self.config=config_input(json.loads(self.path.read_text()),self.root)
@@ -344,6 +345,19 @@ class AgentStore:
344
345
  self._capabilities=self.detector();self._checked=time.monotonic()
345
346
  with self.lock:return {'config':self.config,'providers':self._capabilities,'conversation_id':self.conversation,'messages':self.messages.copy(),'active_turn':self.active,'tool_status':self.tool_status}
346
347
 
348
+ def models(self, provider, recheck=False):
349
+ if provider not in PROVIDERS:raise JobError('invalid_provider','Choose Codex or Claude.')
350
+ with self._model_lock:
351
+ row=next((row for row in self.status(recheck)['providers'] if row['id']==provider),None)
352
+ if not row or not row['installed'] or not row['supported']:
353
+ return {'models':[], 'note':'Install a supported CLI to load model choices. The default model remains available.'}
354
+ cached=self._model_cache.get(provider)
355
+ if not recheck and cached and time.monotonic()-cached[0]<60:return cached[1]
356
+ from kira_models import catalog
357
+ result=catalog(provider)
358
+ self._model_cache[provider]=(time.monotonic(),result)
359
+ return result
360
+
347
361
  def fingerprint(self,config):return hashlib.sha256(json.dumps(config,sort_keys=True).encode()).hexdigest()
348
362
 
349
363
  def stop(self):
package/kira_cli.py CHANGED
@@ -232,10 +232,9 @@ def main():
232
232
  if args.command=='config':
233
233
  file=Path(args.file).expanduser().resolve()
234
234
  if not file.is_file():raise ValueError('Secret environment file does not exist.')
235
- from wallet import ALCHEMY
236
- cfg['secret_env_file']=str(file);cfg['discovery']={'provider':'alchemy','key_env':args.key_env,'explorers':True}
237
- cfg['rpc']={'mode':'custom','allow_public_fallback':True,
238
- 'chains':{str(cid):{'url_env':args.key_env,'alchemy_network':name} for cid,name in ALCHEMY.items()}}
235
+ from kira_config import provider_config
236
+ cfg=provider_config(cfg,'alchemy',args.key_env)
237
+ cfg['secret_env_file']=str(file);cfg['discovery']['explorers']=True
239
238
  elif args.command=='discovery':cfg['discovery']={'provider':args.provider,'key_env':args.key_env,'explorers':args.explorers}
240
239
  elif args.setting=='public':cfg['rpc']['mode']='public'
241
240
  else:
package/kira_config.py CHANGED
@@ -19,6 +19,19 @@ def default_config():
19
19
  return {'schema_version':1,'rpc':{'mode':'public','allow_public_fallback':True,'chains':{}},
20
20
  'discovery':{'provider':'none','key_env':'ALCHEMY_API_KEY','explorers':False}}
21
21
 
22
+ def provider_config(config, provider, key_env):
23
+ """One settings transition. Preserve explicit fallback and custom overrides."""
24
+ result={**config,'rpc':{**config['rpc'],'chains':{cid:dict(row) for cid,row in config['rpc']['chains'].items()}},
25
+ 'discovery':{**config['discovery'],'provider':'alchemy' if provider=='alchemy' else 'none','key_env':key_env}}
26
+ result['rpc']['mode']='custom' if provider=='alchemy' else 'public'
27
+ if provider=='alchemy':
28
+ from wallet import ALCHEMY
29
+ for cid,network in ALCHEMY.items():
30
+ previous=result['rpc']['chains'].get(str(cid))
31
+ if not previous or previous.get('alchemy_network'):
32
+ result['rpc']['chains'][str(cid)]={'url_env':key_env,'alchemy_network':network}
33
+ return result
34
+
22
35
  def load_config():
23
36
  path=config_path()
24
37
  if not path.exists():return default_config()
package/kira_jobs.py CHANGED
@@ -19,7 +19,7 @@ import uuid
19
19
 
20
20
  ASSETS = Path(__file__).resolve().parent
21
21
  State = Literal['queued', 'running', 'interrupted', 'succeeded', 'partial', 'failed', 'cancelled']
22
- Operation = Literal['wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery']
22
+ Operation = Literal['wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery','settings.provider']
23
23
  TERMINAL = {'succeeded', 'partial', 'failed', 'cancelled', 'interrupted'}
24
24
  ADDRESS = re.compile(r'0x[0-9a-fA-F]{40}')
25
25
 
@@ -124,10 +124,10 @@ class JobStore:
124
124
  if not isinstance(job,dict) or type(job.get('schema_version')) is not int or job.get('schema_version') != 1: raise JobError('unsupported_version', 'Unsupported persisted job version.')
125
125
  required={'schema_version','job_id','operation','input','state','attempt','sequence','result','previous_result','key_hash','request_hash','created_at','updated_at','cancel_requested','checkpoint','stage','events','errors','chains'}
126
126
  optional={'raw_hash','control_receipts','control_raw','provider_configuration','started_at'}
127
- if required-set(job) or set(job)-required-optional or job['job_id']!=job_id or job['checkpoint']!='snapshots/jobs/'+job_id or not isinstance(job['operation'],str) or job['operation'] not in {'wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery'} or not isinstance(job['state'],str) or job['state'] not in TERMINAL|{'queued','running'} or any(type(job[k]) is not int or job[k]<0 for k in ('attempt','sequence')) or type(job['cancel_requested']) is not bool or not isinstance(job['input'],dict) or any(not isinstance(job[k],list) for k in ('events','errors')) or not isinstance(job['chains'],dict):
127
+ if required-set(job) or set(job)-required-optional or job['job_id']!=job_id or job['checkpoint']!='snapshots/jobs/'+job_id or not isinstance(job['operation'],str) or job['operation'] not in {'wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery','settings.provider'} or not isinstance(job['state'],str) or job['state'] not in TERMINAL|{'queued','running'} or any(type(job[k]) is not int or job[k]<0 for k in ('attempt','sequence')) or type(job['cancel_requested']) is not bool or not isinstance(job['input'],dict) or any(not isinstance(job[k],list) for k in ('events','errors')) or not isinstance(job['chains'],dict):
128
128
  raise JobError('invalid_envelope','Persisted job validation failed. Preserve the file for recovery.')
129
129
  names={'wallet.add':['address','tag'],'wallet.refresh':['wallet'],'prices.refresh':['wallet'],'wallet.setTags':['wallet','tags'],
130
- 'settings.rpc':['mode','chains','allow_public_fallback'],'settings.discovery':['provider','key_env']}[job['operation']]
130
+ 'settings.rpc':['mode','chains','allow_public_fallback'],'settings.discovery':['provider','key_env'],'settings.provider':['provider','key_env']}[job['operation']]
131
131
  fields(job['input'],names)
132
132
  wallet=job['input'].get('wallet') or job['input'].get('address')
133
133
  if wallet is not None and (not isinstance(wallet,str) or not ADDRESS.fullmatch(wallet)):raise JobError('invalid_envelope','Persisted wallet identity is invalid.')
@@ -154,6 +154,11 @@ class JobStore:
154
154
  return matches[0]
155
155
 
156
156
  def normalize(self, operation, value):
157
+ if operation == 'settings.provider':
158
+ fields(value,['provider','key_env'])
159
+ if value['provider'] not in ('public','alchemy') or not isinstance(value['key_env'],str) or not re.fullmatch(r'[A-Z_][A-Z0-9_]{0,99}',value['key_env']):
160
+ raise JobError('invalid_settings','Choose a data provider and a local credential reference.')
161
+ return value.copy()
157
162
  if operation == 'settings.rpc':
158
163
  fields(value,['mode','chains','allow_public_fallback'])
159
164
  if value['mode'] not in ('public','custom') or type(value['allow_public_fallback']) is not bool or not isinstance(value['chains'],list) or len(value['chains'])>30:
@@ -403,7 +408,12 @@ class JobStore:
403
408
  previous=config['rpc']['chains'].get(key,{})
404
409
  if row['url_env']==previous.get('url_env') and previous.get('alchemy_network'):row['alchemy_network']=previous['alchemy_network']
405
410
  config['rpc']=updated
406
- else:config['discovery']={**job['input'],'explorers':False}
411
+ elif job['operation']=='settings.discovery':config['discovery']={**job['input'],'explorers':config['discovery'].get('explorers',False)}
412
+ else:
413
+ from kira_config import provider_config,secret_values
414
+ config=provider_config(config,**job['input'])
415
+ if job['input']['provider']=='alchemy' and not secret_values(config).get(job['input']['key_env']):
416
+ raise JobError('connection_key_missing','Kira cannot find the local Alchemy key. Follow the connection guide, then retry. Existing settings are unchanged.')
407
417
  atomic(config_path(),config)
408
418
  result={'status':'completed','setting':job['operation']};atomic(self.jobs/'results'/(job['job_id']+'.json'),result);return result
409
419
  env = {**os.environ, 'KIRA_DATA_DIR': str(self.root), 'KIRA_JOB_ID': job['job_id'], 'KIRA_JOB_SNAPSHOT': job['checkpoint'], 'KIRA_ANALYSIS_FD': str(writer.fileno()), 'PYTHONUNBUFFERED': '1'}
package/kira_models.py ADDED
@@ -0,0 +1,86 @@
1
+ """Bounded native model metadata. No inference, threads or credential copying."""
2
+ import json
3
+ import os
4
+ import re
5
+ import selectors
6
+ import shutil
7
+ import signal
8
+ import subprocess
9
+ import tempfile
10
+ import time
11
+
12
+ MODEL = re.compile(r'[a-zA-Z0-9._:/-]{1,100}')
13
+
14
+ def clean_catalog(rows):
15
+ result=[]; seen=set()
16
+ if not isinstance(rows,list):return result
17
+ for row in rows[:100]:
18
+ if not isinstance(row,dict) or row.get('hidden'):continue
19
+ model=row.get('model') or row.get('id')
20
+ if not isinstance(model,str) or not MODEL.fullmatch(model) or model in seen:continue
21
+ name=row.get('displayName') or model
22
+ if not isinstance(name,str):name=model
23
+ name=' '.join(name.split())[:100]
24
+ result.append({'id':model,'name':name or model});seen.add(model)
25
+ if len(result)==40:break
26
+ return result
27
+
28
+ def codex_catalog(path, env, *, timeout=8):
29
+ from kira_agent import CODEX_DISABLED
30
+ argv=[path,'app-server','--listen','stdio://','-c','analytics.enabled=false',
31
+ '-c','history.persistence="none"','-c','mcp_servers={}', '-c','model_provider="openai"']
32
+ for feature in CODEX_DISABLED:argv+=['-c',f'features.{feature}=false']
33
+ with tempfile.TemporaryDirectory(prefix='kira-models-') as cwd:
34
+ child=subprocess.Popen(argv,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,
35
+ cwd=cwd,env=env,start_new_session=True)
36
+ selector=selectors.DefaultSelector();selector.register(child.stdout,selectors.EVENT_READ)
37
+ def send(value):
38
+ child.stdin.write((json.dumps(value)+'\n').encode());child.stdin.flush()
39
+ try:
40
+ send({'id':1,'method':'initialize','params':{'clientInfo':{'name':'kira_model_picker','version':'0.1.5'}}})
41
+ deadline=time.monotonic()+timeout;buffer=b'';received=0;requested=False
42
+ while time.monotonic()<deadline:
43
+ for key,_ in selector.select(min(.2,max(0,deadline-time.monotonic()))):
44
+ chunk=os.read(key.fileobj.fileno(),65536)
45
+ if not chunk:raise ValueError('Catalog ended.')
46
+ received+=len(chunk)
47
+ if received>262144:raise ValueError('Catalog too large.')
48
+ buffer+=chunk
49
+ while b'\n' in buffer:
50
+ line,buffer=buffer.split(b'\n',1)
51
+ try:message=json.loads(line)
52
+ except ValueError:continue
53
+ if not isinstance(message,dict):continue
54
+ if message.get('id')==1 and not requested:
55
+ if 'error' in message:raise ValueError('Initialization failed.')
56
+ requested=True
57
+ send({'method':'initialized'})
58
+ send({'id':2,'method':'model/list','params':{'limit':100,'includeHidden':False}})
59
+ elif message.get('id')==2 and requested:
60
+ if 'error' in message:raise ValueError('Catalog unavailable.')
61
+ result=message.get('result')
62
+ return clean_catalog(result.get('data')) if isinstance(result,dict) else []
63
+ raise TimeoutError('Catalog timed out.')
64
+ finally:
65
+ selector.close()
66
+ try:os.killpg(child.pid,signal.SIGTERM)
67
+ except ProcessLookupError:pass
68
+ try:child.wait(timeout=2)
69
+ except subprocess.TimeoutExpired:
70
+ try:os.killpg(child.pid,signal.SIGKILL)
71
+ except ProcessLookupError:pass
72
+ child.wait()
73
+ child.stdin.close();child.stdout.close()
74
+
75
+ def catalog(provider):
76
+ from kira_agent import native_env
77
+ if provider=='claude':
78
+ return {'models':[{'id':'sonnet','name':'Sonnet'}, {'id':'opus','name':'Opus'}, {'id':'haiku','name':'Haiku'}],
79
+ 'note':'Claude CLI model choices. Check a response to verify access for your account.'}
80
+ if provider!='codex':raise ValueError('Unsupported provider.')
81
+ try:
82
+ path=shutil.which('codex')
83
+ models=codex_catalog(path,native_env()) if path else []
84
+ except (OSError,ValueError,TypeError,TimeoutError):models=[]
85
+ return {'models':models,'note':'CLI catalog choices. Check a response to verify account access.' if models else
86
+ 'Model choices are unavailable. Use the default model, recheck, or enter an ID under Advanced.'}
package/onchain.cjs CHANGED
@@ -52,7 +52,12 @@ function safeError(error){
52
52
  message=message.replace(/https?:\/\/[^\s"<>]+/g,'[RPC URL]');
53
53
  message=message.replace(/(bearer\s+)[^\s,;"<>]+/gi,'$1[redacted]');
54
54
  message=message.replace(/(authorization|api[-_]?key|bearer)([\s:=]+)[^\s,;"<>]+/gi,'$1$2[redacted]');
55
- return {error_type:error.name,message:message.slice(0,500)};
55
+ const diagnostic={error_type:error.name,message:message.slice(0,500)};
56
+ for(let cause=error,depth=0;cause&&depth<6;cause=cause.cause,depth++){
57
+ if(Number.isInteger(cause.status))diagnostic.http_status=cause.status;
58
+ if(Number.isInteger(cause.code))diagnostic.rpc_code=cause.code;
59
+ }
60
+ return diagnostic;
56
61
  }
57
62
  function rpcEndpointsUsed(c){return [...(endpointReads.get(c)||[])].filter(i=>i>=0).sort((a,b)=>a-b);}
58
63
  module.exports={viem,root,assets,networks,bondAbi,endpoints,client,connect,resilientTransport,rpcEndpointsUsed,serialize,safeError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kira-wallet",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Manage your wallets with your own AI. Track tokens, research markets and refresh holdings in a local workspace.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -55,7 +55,9 @@
55
55
  "kira_research_tools.py",
56
56
  "kira_ows.py",
57
57
  "ows_bridge.cjs",
58
- "docs/KIRA_LIVE_QA.md"
58
+ "docs/KIRA_LIVE_QA.md",
59
+ "docs/KIRA_DISCOVERY_ACCEPTANCE.md",
60
+ "kira_models.py"
59
61
  ],
60
62
  "scripts": {
61
63
  "test": "python3 -m unittest discover -s . -p 'test_*.py' && python3 -m unittest discover -s viewer -p 'test_*.py' && node test-onchain.cjs && node --check viewer/static/app.js && node --check viewer/static/jobs.js && node test-workspace.cjs && node --check viewer/static/workspace.js && node --check viewer/static/workspace-model.js && node test-watching.cjs && node test-watching-ui.cjs && node --check viewer/static/watching.js && node --check viewer/static/watching-model.js && node --check viewer/static/agent.js && node test-agent-ui.cjs && node test-markdown.cjs && node --check viewer/static/report.js && node test-report-ui.cjs && node test-holdings-ui.cjs && node test-rpc-fallback.cjs && node test-ows.cjs && node test-ows-ui.cjs && node --check viewer/static/ows.js && node --check viewer/static/chat-workspace.js && node --check viewer/static/selects.js && node --check site/demo.js"