@agentshouse/kit 0.1.0-alpha.26 → 0.1.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ The Skill set Kit installs is Matt Pocock's
12
12
  On Linux `amd64` or `arm64`, run the published, versioned bootstrap once:
13
13
 
14
14
  ```sh
15
- bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.26/connect-linux.sh)
15
+ bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.28/connect-linux.sh)
16
16
  ```
17
17
 
18
18
  The bootstrap first establishes Docker Engine from what it observes on the
@@ -51,8 +51,10 @@ the set again.
51
51
  Running a newer release's bootstrap the same way is the only update. When the
52
52
  installed container runs another image, the bootstrap pulls this release's
53
53
  image and asks the running resident Kit to drain with `kit resident drain`. The
54
- resident stops acquiring Runs, resumptions, setup and agent CLI work, while the
55
- Sessions it supervises keep their renewal, owner stops, answers and reports.
54
+ resident stops acquiring Runs, resumptions, setup, agent CLI work, Google
55
+ sign-ins and scheduled Google captures, while the Sessions it supervises keep
56
+ their renewal, owner stops, answers and reports and a capture already running
57
+ finishes.
56
58
  While any Agent Session is still running or unreported, including one whose Run
57
59
  was being acquired when the drain began, the bootstrap names it and leaves the
58
60
  container as it is; rerun it once they end. Once nothing remains,
@@ -141,21 +143,17 @@ anything reaches House.
141
143
 
142
144
  ## Building the package
143
145
 
144
- The repository checkout is the development source. Build the package
145
- that can be published by supplying House's dedicated installed-application
146
- Google OAuth client, its id and its client secret:
146
+ The repository checkout is the development source. Build the package that can
147
+ be published:
147
148
 
148
149
  ```sh
149
- HOUSE_KIT_GOOGLE_OAUTH_CLIENT_ID=kit-client.apps.googleusercontent.com \
150
- HOUSE_KIT_GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-kit-installed-app \
151
150
  npm run build
152
151
  ```
153
152
 
154
- The command writes the publishable package to `dist/package`. Both values are
155
- public instance content fixed into that package at build time: Google issues an
156
- installed application's client secret without treating it as confidential, and
157
- still requires it in every token request. The installed Kit does not read either
158
- from its runtime environment and never requests them from House.
153
+ The command writes the publishable package to `dist/package`. It reads no
154
+ environment and ships no provider OAuth client: each Google account signs in
155
+ with the User's own client, as [the Google preset](#the-google-preset)
156
+ describes.
159
157
 
160
158
  Runtime dependencies stay minimal and exactly pinned in `package.json` and
161
159
  `package-lock.json`: House's own `@agentshouse/mdmodel`, `yaml`, which reads
@@ -254,7 +252,8 @@ mode: `"intermittent"` for a laptop or other normally offline placement,
254
252
  any other value refuses as `kit_configuration_invalid`; `kit apply` sends the
255
253
  declared mode with the rest of the desired state. An
256
254
  instance declares one or more services, and under the `custom` preset each
257
- service owns the command that produces it.
255
+ service owns the command that produces it. The Kit reads only `custom` instances
256
+ and passes over any other entry of `instances`.
258
257
 
259
258
  `host` declares the Linux host this installation measures for
260
259
  [host capacity reporting](#host-capacity). `null` declares that this Kit reports
@@ -360,120 +359,81 @@ refuses as `kit_hook_rejected` instead of leaving whatever was installed before.
360
359
 
361
360
  ## The Google preset
362
361
 
363
- The published Kit is built with one House-owned Google installed-application
364
- OAuth client. Its id and client secret are public build-time instance content,
365
- not House secrets and not values served by House at run time. Google refresh
366
- and access tokens remain in the installation's local credential store.
362
+ House holds this Environment's Google Sources, and its owner sets them in the
363
+ App. Each entry names an account name the owner chooses, one service -
364
+ `gmail`, `calendar` or `drive` - the Rooms it goes to, and what its first
365
+ capture takes: `{ "new": true }` for new activity only, `{ "days": n }`,
366
+ `{ "items": n }` or `{ "all": true }`. Neither the configuration file nor any
367
+ command on this computer declares one.
368
+
369
+ Each service captures a fixed corpus under its own read-only scope: Gmail every
370
+ message but Spam and Trash, Calendar the account's primary calendar, and Drive
371
+ the files the account owns.
372
+
373
+ The resident reads the list from `/kit/sources/desired` each time its Kit
374
+ control stream connects and each time House signals `sources` on it, then
375
+ reconciles:
376
+
377
+ - a new entry gets its local state and is captured once its account is signed
378
+ in for its service;
379
+ - a changed entry's Rooms take effect on its next capture;
380
+ - an entry House no longer lists stops, and its checkpoint, health, gaps,
381
+ dropped items and unsaved originals are deleted;
382
+ - an account left with no entry has each of its grants revoked at Google's
383
+ revocation endpoint and its credentials, its OAuth client included, deleted.
384
+ Google's revocation ends every grant the Google user gave the client's
385
+ project, so when another listed account is signed in to the same Google user,
386
+ the Kit deletes the local credentials and revokes nothing.
387
+
388
+ It then reports every entry to `/kit/sources/report`, and reports an entry
389
+ again after each sign-in and each capture: `signed_out` while its
390
+ account holds no grant for its service, `failed` with its cause code when its
391
+ last capture failed, and `ready` otherwise, each with the account's signed-in
392
+ email once the Kit knows it.
393
+
394
+ The Kit ships no Google OAuth client. Each account signs in with the User's
395
+ own: an OAuth client of type Desktop app in the User's Google Cloud project,
396
+ with the service's API enabled there. An entry whose account holds no grant for
397
+ its service opens a login renewal for integration `google`, instance the
398
+ account name and service the entry's service, and presents its ceremony on
399
+ House's owner-authenticated page, so the owner's browser never has to reach
400
+ this Environment. When the account has no client yet, the page first collects
401
+ the client id and secret. It then links to Google's consent for that service's
402
+ scope only; Google sends the browser to a `http://127.0.0.1:<port>/` address
403
+ that nothing answers, and the owner pastes that whole address into the page.
404
+ The Kit accepts it only when it carries the state its own consent issued,
405
+ exchanges the code with its PKCE verifier and the account's client, and stores
406
+ the grant beside the client in `state/google/credentials/<account>.json`. The
407
+ collected values pass through House's live relay only; neither they nor any
408
+ token enter a report, a log or anything House stores. Adding a
409
+ service to a signed-in account asks only for that service's consent, and each
410
+ service keeps its own grant. An address that answers another consent, or a code
411
+ Google refuses to exchange, stores nothing; a declined consent or a ceremony
412
+ that outlives its ten-minute bound cancels the sign-in, and the Kit tries again
413
+ at the next reconciliation.
414
+
415
+ The resident captures each signed-in entry every 15 minutes, with no command.
416
+ An occurrence never overlaps its entry's previous one, and one missed while the
417
+ resident was stopped is not replayed: the next resumes from the checkpoint. The
418
+ first capture fixes the initial choice's boundary, so `new` captures nothing
419
+ that existed then and everything that arrives after.
367
420
 
368
- Each Google instance represents one Google account, and one Kit installation
369
- may declare as many as the User has. Each instance owns its own local Google
370
- authorization, selectors, Source references, Room routes, checkpoints and
371
- health under `state/<instance>/`; the installation's one `kit login` covers
372
- them all, and no instance can read another's provider credential.
373
-
374
- ```json
375
- {
376
- "availability": "intermittent",
377
- "execution_authority": "refuse",
378
- "migrators": [],
379
- "instances": [
380
- {
381
- "id": "google-account",
382
- "preset": "google",
383
- "scopes": ["https://www.googleapis.com/auth/contacts.readonly"],
384
- "services": [
385
- {
386
- "service": "gmail",
387
- "source_ref": "src_google_mail",
388
- "rooms": ["work-room"],
389
- "backfill": { "days": 30 },
390
- "selection": { "labels": ["INBOX"] }
391
- },
392
- {
393
- "service": "calendar",
394
- "source_ref": "src_google_calendar",
395
- "rooms": ["work-room"],
396
- "backfill": { "all": true },
397
- "selection": { "calendars": ["primary"] }
398
- },
399
- {
400
- "service": "drive",
401
- "source_ref": "src_google_drive",
402
- "rooms": ["work-room"],
403
- "backfill": { "count": 500 },
404
- "selection": { "files": ["file-id"], "folders": ["folder-id"] }
405
- }
406
- ]
407
- }
408
- ],
409
- "clients": [],
410
- "host": null,
411
- "skills": false
412
- }
421
+ ```sh
422
+ kit source capture work # one occurrence of every entry of the account now
423
+ kit source capture work gmail # one occurrence of one entry now
424
+ kit source status work
413
425
  ```
414
426
 
415
- Google authorization is incremental. Enabling Gmail requests only Gmail's
416
- read-only scope; Calendar and Drive request their own read-only scopes only
417
- when enabled. Each service scope set has its own local provider grant, so a
418
- later authorization never widens or replaces a sibling service's grant.
419
- Additional Google API scopes are declared once on the preset instance and
420
- remain local; a declared service names only scopes that instance already
421
- holds. Drive requires at least one explicit file or folder selection and never
422
- defaults to the whole account.
427
+ `kit source capture` and `kit source status` take the account name where a
428
+ custom connector takes its instance; a name House lists as an account is read
429
+ as the account. While the resident runs, it performs the requested occurrence
430
+ itself, after any occurrence of that entry already running.
423
431
 
424
- A Google API outside the three curated services may use the closed declared
425
- service schema. The declaration is data only: no script or executable transform
426
- is loaded. Its host must be a Google API domain and it runs under scopes the
427
- instance already holds.
428
-
429
- ```json
430
- {
431
- "service": "people",
432
- "source_ref": "src_google_people",
433
- "rooms": ["work-room"],
434
- "backfill": { "all": true },
435
- "declaration": {
436
- "host": "people.googleapis.com",
437
- "path": "/v1/people/me/connections",
438
- "query": { "personFields": "names,emailAddresses,metadata" },
439
- "page_size": "pageSize",
440
- "scopes": ["https://www.googleapis.com/auth/contacts.readonly"],
441
- "page_token": {
442
- "request": "pageToken",
443
- "response": "nextPageToken",
444
- "checkpoint_request": "syncToken",
445
- "checkpoint_response": "nextSyncToken"
446
- },
447
- "items": "connections",
448
- "fields": {
449
- "native_id": "resourceName",
450
- "native_version": "etag",
451
- "provider_modified_at": "metadata.sources.0.updateTime",
452
- "observed_at": "metadata.sources.0.updateTime",
453
- "body": ["names.0.displayName", "emailAddresses.0.value"],
454
- "container": "metadata.sources.0.type"
455
- }
456
- }
457
- }
458
- ```
459
-
460
- A Google instance signs in only through [Environment setup](#environment-setup),
461
- on House's owner-authenticated page, so the owner's browser never has to reach
462
- this Environment. For each enabled service whose scopes the instance does not
463
- hold yet, the page links to Google's consent for exactly that service's scopes.
464
- Google then sends the browser to a `http://127.0.0.1:<port>/` address that
465
- nothing answers: the owner copies that whole address from the address bar and
466
- pastes it into the page. The Kit accepts it only when it carries the state its
467
- own consent issued, exchanges the code with its PKCE verifier and the built
468
- client, and stores the grant locally. An address that answers another consent,
469
- or a code Google refuses to exchange, fails the login and stores nothing; a
470
- declined consent cancels it. Grants already completed for earlier services stay.
471
-
472
- `kit source sign-in <instance>` signs in a custom connector instance through
473
- its own command, described below. It skips an instance whose local
474
- authorization is already valid and answers `ready`, `skipped`, or
475
- `needs_attention`. A Google instance refuses it as `kit_connector_login_guided`
476
- and names House setup.
432
+ A failed occurrence records its cause. A Google 403 for an API disabled in the
433
+ client's project fails as `api_disabled`, and `kit source status` names the API
434
+ to enable; a revoked scope (`google_scope_revoked`), an expired or revoked grant
435
+ (`google_authority_revoked`) and a provider failure
436
+ (`google_provider_unavailable`) keep their own codes.
477
437
 
478
438
  ## Environment setup
479
439
 
@@ -486,7 +446,9 @@ kit setup
486
446
  ```
487
447
 
488
448
  `kit worker run` performs the same step at the start of a pass, before it
489
- hands out any work, so a managed Environment needs no terminal. The walk first
449
+ hands out any work, so a managed Environment needs no terminal; the resident
450
+ Kit performs it each time its control stream connects and on each `runs`
451
+ signal, which House also sends for every setup request. The walk first
490
452
  prepares the absolute `/agents/house` base, then visits every declared agent
491
453
  CLI and then every connector instance, in configuration order:
492
454
 
@@ -590,11 +552,46 @@ An object whose content changes without its native version changing keeps its
590
552
  key and is refused by House as a delivery-key conflict rather than silently
591
553
  overwriting the earlier Record.
592
554
 
593
- The same command owns its module authorization. `kit source sign-in` and the
594
- setup walk's status check send one JSON line
555
+ The same command owns its module authorization. `kit source sign-in <instance>`
556
+ skips an instance whose local authorization is already valid and answers
557
+ `ready`, `skipped`, or `needs_attention`. It and the setup walk's status check
558
+ send one JSON line
595
559
  with `action: "authorization"` and `mode: "status"` or `mode: "login"`; the
596
560
  command returns exactly one JSON object such as `{ "authorized": true }`.
597
561
 
562
+ ## The Kit control stream
563
+
564
+ The resident Kit holds one `/kit/stream` WebSocket for its Environment,
565
+ authenticated with its Environment control lease and the Kit contract header,
566
+ for as long as it holds that lease; `kit worker run` holds one for the length
567
+ of its pass to receive its owner's commands. The Kit sends nothing on it and
568
+ accepts every frame House sends.
569
+
570
+ Each time the socket connects, the resident acquires everything a signal could
571
+ have named, because a signal House sent while the socket was down is lost: its
572
+ Runs and resumptions with their setup and conversation files, the owner's
573
+ pending commands, the agent CLI list, the saved working copy choice and its
574
+ Google Sources. After that it acquires only what House names. A
575
+ `work_available` frame for `agents`, `working_copy` or `sources` acquires that
576
+ subject alone. `runs` takes a resumption or else a waiting Run, readying the
577
+ agent CLIs, a requested setup and conversation files first. `commands` hands
578
+ the supervised Session its stops, interrupts and answers and takes a
579
+ resumption the same way, without acquiring a Run. An `entries` frame, a change
580
+ in a Room the lease admits, delivers the working copies. A pass that took a Run
581
+ acquires again once that Run ends, and a pass that found nothing waits for the
582
+ next signal. A Run outcome House did not take is reported again every 30
583
+ seconds until House answers. The Kit has no idle acquisition cycle, no command
584
+ poll and no Session heartbeat: a Session is observed when its process starts
585
+ and when its supervisor sees it stop.
586
+
587
+ A socket that closes or never opens reconnects after a delay drawn between half
588
+ and all of one second, doubled for each consecutive failure up to 30 seconds.
589
+ An `authority_changed` close reconnects after a delay drawn between 0 and 2
590
+ seconds, with no error backoff. A `replaced` close, which means a newer
591
+ connection holds the Environment, ends the resident with `kit_stream_replaced`,
592
+ and an upgrade House refuses with `kit_upgrade_required` stops every House call
593
+ as [above](#connect-a-linux-computer) describes; neither is retried.
594
+
598
595
  ## The Worker runtime
599
596
 
600
597
  ```sh
@@ -1018,8 +1015,9 @@ A save that fails leaves one note in `state/conversation-originals/` naming the
1018
1015
  attachment, its Run, the local file and the descriptor it declared. Each
1019
1016
  later `kit worker run` retries it from that file without materializing or
1020
1017
  acknowledging anything again, and reports `source_unavailable` when
1021
- the file is gone or changed rather than saving different bytes. While a
1022
- WorkerSession stays supervised the same retry runs at the heartbeat interval. The note leaves once
1018
+ the file is gone or changed rather than saving different bytes. When a
1019
+ supervised WorkerSession ends, the same retry runs once more before its Run is
1020
+ reported. The note leaves once
1023
1021
  House records the save, once House answers that the Run is over, or when House
1024
1022
  authorizes that Run's cache cleanup; House accepts no save for a Run that has
1025
1023
  settled, so an unsaved original keeps its failed outcome from then on. The owner
@@ -1155,7 +1153,10 @@ written into the durable binding. Reconciliation must obtain current House
1155
1153
  authority before the live process can make further calls.
1156
1154
 
1157
1155
  An owner stop requested through House reaches this Kit as a durable command,
1158
- both at startup and while a Session is supervised. The Kit destroys the exact
1156
+ both at startup and while a Session is supervised: House signals `commands` on
1157
+ the Kit control stream and the supervisor acquires the pending stops,
1158
+ interrupts and answers at once, so a stop reaches the process within a second.
1159
+ The Kit destroys the exact
1159
1160
  tmux session, confirms that it is gone, and reports the stopped Session. It
1160
1161
  reports no continuation for a Run stopped by its owner and never reopens the
1161
1162
  local channel for that Session.
@@ -1209,8 +1210,8 @@ on the door's own deduplication. When House stops a batch early, its answer
1209
1210
  settles only the prefix it reached, and the unsettled tail is reoffered with the
1210
1211
  rest. The loop repeats while the connector still returns items and no Room
1211
1212
  destination is blocked, and every run ends by writing the service's health into
1212
- this installation's own state. House holds no preset, service, route, checkpoint
1213
- or health of yours.
1213
+ this installation's own state. House holds no custom connector's service, route,
1214
+ checkpoint or health, and no Google entry's checkpoint or health.
1214
1215
 
1215
1216
  A Capture item that declares a `file` attachment answers one Record per Room
1216
1217
  destination: Rooms of one owner share that owner's Record, and a Room of another
@@ -1316,8 +1317,8 @@ The Environment owner chooses in House which Rooms this Environment keeps
1316
1317
  locally, and whether it keeps `/private`. An Environment whose owner never chose
1317
1318
  copies nothing, and a Room joined later stays uncopied until the owner selects
1318
1319
  it. The resident Kit reads the saved choice through
1319
- `POST /kit/working-copy/selection` when it starts, whenever its control stream
1320
- connects or reconnects, and whenever House points it there after a save;
1320
+ `POST /kit/working-copy/selection` whenever its control stream connects or
1321
+ reconnects and whenever House points it there after a save;
1321
1322
  `kit worker run` reads it once before its pass.
1322
1323
 
1323
1324
  Each selected Room becomes one ordinary Git repository at
@@ -1346,8 +1347,9 @@ state. The House revision of every file content Kit received, keyed by path,
1346
1347
  Git blob and the House state that received it, stays in Kit home under
1347
1348
  `state/working-copies/`, outside the repository. A local commit is a local tree, never a House revision.
1348
1349
 
1349
- The resident Kit keeps every active copy current. On each pass, and whenever
1350
- it reads the saved choice, one `POST /kit/door/discover` tells it where each
1350
+ The resident Kit keeps every active copy current. Whenever its control stream
1351
+ carries a Room change, and whenever it reads the saved choice, one
1352
+ `POST /kit/door/discover` tells it where each
1351
1353
  copied Room stands, and for a Room that moved on it reads the changes after the
1352
1354
  copy's recorded position through `POST /kit/door/enumerate`. A changed, new or
1353
1355
  removed authored file becomes a commit on `refs/house/received`, its House
@@ -1366,7 +1368,8 @@ or protected paths House declares for the Room change, Kit reads the Room again
1366
1368
  as a reselection does. A position older than House
1367
1369
  retains, or from before House's database was restored, makes Kit read the Room
1368
1370
  again as a reselection does; while House's replica has not reached the position
1369
- the copy waits for the next pass. A Room House no longer answers under its
1371
+ the copy waits, and the resident delivers again 30 seconds later. A Room House
1372
+ no longer answers under its
1370
1373
  recorded handle makes Kit read the saved choice again, which follows a handle
1371
1374
  change or marks the copy inactive, and a Room that lost sync is named on
1372
1375
  standard error as `operation_denied` and keeps what it holds. `kit worker run`
@@ -1755,7 +1758,13 @@ $HOUSE_KIT_HOME/
1755
1758
  state/skill-set.json
1756
1759
  state/how-we-work
1757
1760
  state/continuations/<run identity>.json
1758
- state/<instance>/credentials/google.json
1761
+ state/google/entries/<source>.json
1762
+ state/google/credentials/<account>.json
1763
+ state/google/checkpoints/<source>.json
1764
+ state/google/health/<source>.json
1765
+ state/google/gaps/<source>.json
1766
+ state/google/dropped/<source>.json
1767
+ state/google/originals/<source>.json
1759
1768
  state/<instance>/checkpoints/<service>.json
1760
1769
  state/<instance>/health/<service>.json
1761
1770
  state/<instance>/gaps/<service>.json
@@ -1765,15 +1774,18 @@ $HOUSE_KIT_HOME/
1765
1774
 
1766
1775
  Every file is written atomically and readable only by its owner. A file the Kit
1767
1776
  cannot read is refused rather than treated as a service that never captured.
1768
- Disabling a service or an instance leaves its state in place, so re-enabling
1769
- resumes from the stored cursor instead of importing again.
1777
+ A Google entry's state is keyed by its Source reference and lives only while
1778
+ House lists the entry. Disabling a custom service or instance leaves its state
1779
+ in place, so re-enabling resumes from the stored cursor instead of importing
1780
+ again.
1770
1781
 
1771
1782
  ```sh
1772
1783
  kit source reset acme # every service
1773
1784
  kit source reset acme notes # one service
1774
1785
  ```
1775
1786
 
1776
- Reset is the only deletion of captured state. It removes the named checkpoints
1787
+ Reset serves custom connectors, and is the only deletion of their captured
1788
+ state. It removes the named checkpoints
1777
1789
  and their outstanding retained references so the next capture restarts exactly at
1778
1790
  the configured backfill boundary. It takes the same
1779
1791
  Environment control lease a capture takes, so it cannot delete a checkpoint a
@@ -1993,10 +2005,7 @@ workspaces.
1993
2005
  ```sh
1994
2006
  docker compose -f test/compose.yaml build kit
1995
2007
  docker compose -f test/compose.yaml run --rm kit npm test
1996
- docker compose -f test/compose.yaml run --rm \
1997
- -e HOUSE_KIT_GOOGLE_OAUTH_CLIENT_ID=kit-client.apps.googleusercontent.com \
1998
- -e HOUSE_KIT_GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-kit-installed-app \
1999
- kit npm run build
2008
+ docker compose -f test/compose.yaml run --rm kit npm run build
2000
2009
  ```
2001
2010
 
2002
2011
  `test/pair/run.sh` boots real Core, Auth and PostgreSQL and runs the installed
package/bin/kit.js CHANGED
@@ -5,12 +5,12 @@ import { join, resolve } from "node:path";
5
5
  import { connectHouse, loginHouse, storedEnvironment, storedHouse } from "../src/login.js";
6
6
  import { apply } from "../src/apply.js";
7
7
  import { captureSource, resetSource, retrySourceOriginals, sourceStatus } from "../src/source.js";
8
+ import { accountEntries, accountStatus, captureAccount } from "../src/google/entries.js";
8
9
  import { setupEnvironment } from "../src/setup.js";
9
10
  import { signInConnector } from "../src/connector-login.js";
10
11
  import { attachSession } from "../src/attach.js";
11
12
  import { runResident, runWorkers } from "../src/worker.js";
12
13
  import { openSession, releaseControl, requestDrain } from "../src/house.js";
13
- import { GOOGLE_OAUTH_CLIENT } from "../src/instance/google.js";
14
14
  import { reportHost } from "../src/host.js";
15
15
  import { signInAgent, updateAgents } from "../src/agents.js";
16
16
  import { serveWorkspaces } from "../src/workspace.js";
@@ -37,8 +37,8 @@ const AGENTS_USAGE =
37
37
  "usage: kit agents update [--config <path>] | kit agents sign-in <kind> [--config <path>]";
38
38
  const WORKSPACE_USAGE = "usage: kit workspace host [--config <path>] [--passes <count>]";
39
39
  const SOURCE_USAGE =
40
- "usage: kit source <capture|reset|retry-files> <instance> [service] [--config <path>]\n" +
41
- " kit source status <instance> [service] [--config <path>]\n" +
40
+ "usage: kit source <capture|status> <account|instance> [service] [--config <path>]\n" +
41
+ " kit source <reset|retry-files> <instance> [service] [--config <path>]\n" +
42
42
  " kit source sign-in <instance> [--config <path>]";
43
43
  const SETUP_USAGE = "usage: kit setup [--config <path>]";
44
44
  const REPOSITORY_USAGE =
@@ -108,13 +108,6 @@ function write(line) {
108
108
  process.stdout.write(`${line}\n`);
109
109
  }
110
110
 
111
- function builtGoogleOAuthClient() {
112
- if (GOOGLE_OAUTH_CLIENT === null) {
113
- throw new KitRefusal("google_oauth_client_unconfigured", "run npm run build");
114
- }
115
- return GOOGLE_OAUTH_CLIENT;
116
- }
117
-
118
111
  async function runLogin(args, directory) {
119
112
  const { values } = declared(args, ["--house", "--manual", "--host-browser", "--environment"], 0, { flags: ["--host-browser"] });
120
113
  if (values["--manual"] && values["--host-browser"]) throw new Error(LOGIN_USAGE);
@@ -188,15 +181,12 @@ async function runSetup(args, directory) {
188
181
  process.once("SIGINT", cancel);
189
182
  let setup;
190
183
  try {
191
- setup = await setupEnvironment(
192
- {
193
- house,
194
- directory,
195
- configuration: values["--config"] ?? join(directory, "kit.json"),
196
- signal: cancellation.signal,
197
- },
198
- { googleOAuthClient: builtGoogleOAuthClient },
199
- );
184
+ setup = await setupEnvironment({
185
+ house,
186
+ directory,
187
+ configuration: values["--config"] ?? join(directory, "kit.json"),
188
+ signal: cancellation.signal,
189
+ });
200
190
  } finally {
201
191
  process.removeListener("SIGINT", cancel);
202
192
  }
@@ -268,22 +258,27 @@ async function runSourceCapture(args, directory) {
268
258
  const house = await storedHouse(directory);
269
259
  const cancellation = new AbortController();
270
260
  process.once("SIGINT", () => cancellation.abort());
271
- const result = await captureSource(
272
- {
273
- house,
274
- directory,
275
- configuration: values["--config"] ?? join(directory, "kit.json"),
276
- instance: rest[0],
277
- service: rest[1] ?? null,
278
- signal: cancellation.signal,
279
- },
280
- { googleOAuthClient: builtGoogleOAuthClient },
281
- );
261
+ const entries = await accountEntries(directory, rest[0]);
262
+ const options = {
263
+ house,
264
+ directory,
265
+ service: rest[1] ?? null,
266
+ signal: cancellation.signal,
267
+ };
268
+ const result =
269
+ entries.length > 0
270
+ ? await captureAccount({ ...options, account: rest[0], entries })
271
+ : await captureSource({
272
+ ...options,
273
+ configuration: values["--config"] ?? join(directory, "kit.json"),
274
+ instance: rest[0],
275
+ });
282
276
  for (const delivered of result.services) {
283
277
  const category =
284
278
  delivered.health.category === null ? "" : ` (${delivered.health.category})`;
279
+ const cause = delivered.health.cause === undefined ? "" : `, cause ${delivered.health.cause}`;
285
280
  write(
286
- `Delivered ${delivered.delivered} items for ${result.instance}/${delivered.service} in ${delivered.batches} batches; health ${delivered.health.state}${category}.`,
281
+ `Delivered ${delivered.delivered} items for ${result.instance}/${delivered.service} in ${delivered.batches} batches; health ${delivered.health.state}${category}${cause}.`,
287
282
  );
288
283
  if (delivered.refused !== null) {
289
284
  write(
@@ -332,22 +327,32 @@ async function runSourceRetryFiles(args, directory) {
332
327
  async function runSourceStatus(args, directory) {
333
328
  const { values, rest } = declared(args, ["--config"], 2);
334
329
  if (rest.length === 0) throw new Error(SOURCE_USAGE);
335
- const result = await sourceStatus({
336
- directory,
337
- configuration: values["--config"] ?? join(directory, "kit.json"),
338
- instance: rest[0],
339
- service: rest[1] ?? null,
340
- });
330
+ const entries = await accountEntries(directory, rest[0]);
331
+ const result =
332
+ entries.length > 0
333
+ ? await accountStatus({ directory, account: rest[0], entries, service: rest[1] ?? null })
334
+ : await sourceStatus({
335
+ directory,
336
+ configuration: values["--config"] ?? join(directory, "kit.json"),
337
+ instance: rest[0],
338
+ service: rest[1] ?? null,
339
+ });
341
340
  for (const reported of result.services) {
342
341
  const category = reported.health.category === null ? "" : ` (${reported.health.category})`;
342
+ const cause = reported.health.cause === undefined ? "" : `, cause ${reported.health.cause}`;
343
343
  const observed =
344
344
  reported.health.observed_at === null ? "never observed" : reported.health.observed_at;
345
+ const signedIn =
346
+ reported.signed_in === undefined ? "" : reported.signed_in ? "signed in; " : "signed out; ";
345
347
  write(
346
348
  `${result.instance}/${reported.service} -> ${reported.source_ref} in ${reported.rooms.join(", ")}: ` +
347
- `${reported.health.state}${category} at ${observed}; ` +
349
+ `${signedIn}${reported.health.state}${category}${cause} at ${observed}; ` +
348
350
  `cursor ${reported.checkpoint?.cursor ?? "none"}, ${reported.gaps.length} gaps, ` +
349
351
  `${reported.dropped.length} dropped items, ${reported.originals.length} unsaved originals.`,
350
352
  );
353
+ if (reported.enable !== null && reported.enable !== undefined) {
354
+ write(`Enable the ${reported.enable} in the Google Cloud project of ${result.instance}'s OAuth client.`);
355
+ }
351
356
  }
352
357
  if (result.services.some((reported) => reported.health.state !== "ok")) process.exitCode = 1;
353
358
  }
@@ -425,14 +430,11 @@ function writeConversationFiles(supervised) {
425
430
  async function runWorkerRun(args, directory) {
426
431
  const { values } = declared(args, ["--config"], 0);
427
432
  const house = await storedHouse(directory);
428
- const supervised = await runWorkers(
429
- {
430
- house,
431
- directory,
432
- configuration: values["--config"] ?? join(directory, "kit.json"),
433
- },
434
- { googleOAuthClient: builtGoogleOAuthClient },
435
- );
433
+ const supervised = await runWorkers({
434
+ house,
435
+ directory,
436
+ configuration: values["--config"] ?? join(directory, "kit.json"),
437
+ });
436
438
  write(`Worker runtime ready on tmux ${supervised.tmux} in namespace ${supervised.namespace}.`);
437
439
  if (supervised.epochReport !== null) {
438
440
  write(`House started a new log epoch; reported ${supervised.epochReport.length} held Runs and House settled each.`);
package/bootstrap.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "hosts": {
5
5
  "linux": {
6
- "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.26/connect-linux.sh)",
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.28/connect-linux.sh)",
7
7
  "asset": {
8
8
  "name": "connect-linux.sh",
9
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.26/connect-linux.sh"
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.28/connect-linux.sh"
10
10
  },
11
11
  "workspace": {
12
12
  "argument": "--workspace",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",