@cotal-ai/connector-jcode 0.39.1 → 0.40.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAqE,MAAM,qBAAqB,CAAC;AAmCtH,eAAO,MAAM,+BAA+B,aAQ1C,CAAC;AACH,eAAO,MAAM,oCAAoC,aAAsB,CAAC;AAExE,6FAA6F;AAC7F,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpF;AA+LD,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA+kBlD"}
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAqE,MAAM,qBAAqB,CAAC;AAmCtH,eAAO,MAAM,+BAA+B,aAQ1C,CAAC;AACH,eAAO,MAAM,oCAAoC,aAAsB,CAAC;AAExE,6FAA6F;AAC7F,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpF;AA+LD,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAsmBlD"}
package/dist/host.js CHANGED
@@ -31109,7 +31109,7 @@ var require_dist = __commonJS({
31109
31109
  // src/host.ts
31110
31110
  import { createHash as createHash7, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
31111
31111
  import { spawn as spawn2 } from "node:child_process";
31112
- import { existsSync as existsSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync3, closeSync as closeSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
31112
+ import { existsSync as existsSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync3, closeSync as closeSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
31113
31113
  import { createServer as createServer2 } from "node:net";
31114
31114
  import { join as join6, resolve as resolve3, sep as sep2 } from "node:path";
31115
31115
  import { fileURLToPath } from "node:url";
@@ -42494,18 +42494,22 @@ function mirrorJcodeCredentials(home, sources = credentialSources()) {
42494
42494
  for (const entry of jcodeCredentialMirrorInventory(home, sources))
42495
42495
  copyCredentialFile(home, entry.source, entry.destinationRelative);
42496
42496
  }
42497
+ function reclaimSocketAlias(alias) {
42498
+ let stats;
42499
+ try {
42500
+ stats = lstatSync(alias);
42501
+ } catch (error51) {
42502
+ if (error51.code !== "ENOENT") throw error51;
42503
+ return;
42504
+ }
42505
+ rmSync2(alias, { force: true, recursive: stats.isDirectory() });
42506
+ }
42497
42507
  function shortSocketHome(home) {
42498
42508
  const id = createHash5("sha256").update(resolve2(home)).digest("hex").slice(0, 12);
42499
42509
  const socketDir = join3("/tmp", `jc-${id}`);
42500
42510
  privateDirectory(socketDir, { requireOwner: true, pin: false });
42501
42511
  const alias = join3(socketDir, "home");
42502
- try {
42503
- const stats = lstatSync(alias);
42504
- if (!stats.isSymbolicLink()) throw new Error(`Jcode short socket alias is not a symlink: ${alias}`);
42505
- rmSync2(alias, { force: true });
42506
- } catch (error51) {
42507
- if (error51.code !== "ENOENT") throw error51;
42508
- }
42512
+ reclaimSocketAlias(alias);
42509
42513
  try {
42510
42514
  symlinkSync(home, alias, "dir");
42511
42515
  } catch (error51) {
@@ -42532,7 +42536,7 @@ function shortSocketHome(home) {
42532
42536
 
42533
42537
  // src/private-lifecycle.ts
42534
42538
  import { randomBytes as randomBytes5 } from "node:crypto";
42535
- import { readFileSync as readFileSync3, readdirSync as readdirSync3 } from "node:fs";
42539
+ import { chmodSync as chmodSync2, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
42536
42540
  import { execFileSync as execFileSync2 } from "node:child_process";
42537
42541
  import { join as join4 } from "node:path";
42538
42542
  var isPid = (value) => Number.isInteger(value) && value > 1;
@@ -42704,6 +42708,43 @@ async function waitGone(pids, timeoutMs) {
42704
42708
  await new Promise((resolve4) => setTimeout(resolve4, 50));
42705
42709
  }
42706
42710
  }
42711
+ var LAUNCH_RECORD_FILE = "cotal-launch.json";
42712
+ function recordLaunch(home, record2) {
42713
+ const path4 = join4(home, LAUNCH_RECORD_FILE);
42714
+ writeFileSync3(path4, `${JSON.stringify(record2)}
42715
+ `, { mode: 384 });
42716
+ chmodSync2(path4, 384);
42717
+ }
42718
+ function readLaunchRecord(home) {
42719
+ let parsed;
42720
+ try {
42721
+ parsed = JSON.parse(readFileSync3(join4(home, LAUNCH_RECORD_FILE), "utf8"));
42722
+ } catch {
42723
+ return void 0;
42724
+ }
42725
+ const record2 = parsed;
42726
+ const identity = record2?.identity;
42727
+ const host = record2?.host;
42728
+ if (typeof identity !== "string" || !identity) return void 0;
42729
+ return isPid(host?.pid) && typeof host?.start === "string" && host.start ? { identity, host: { pid: host.pid, start: host.start } } : void 0;
42730
+ }
42731
+ async function stopOrphanedTree(options) {
42732
+ const { home, gracefulWaitMs = 3e3, killWaitMs = 2e3 } = options;
42733
+ const record2 = readLaunchRecord(home);
42734
+ if (!record2) return [];
42735
+ if (alive(record2.host.pid)) return [];
42736
+ const targets = captureLaunchProcesses(record2.identity).filter((identity) => identity.pid !== process.pid && processMatches(identity) && alive(identity.pid)).map((identity) => identity.pid);
42737
+ if (!targets.length) return [];
42738
+ signalTree(targets, "SIGTERM");
42739
+ if (!await waitGone(targets, gracefulWaitMs)) {
42740
+ signalTree(targets.filter(alive), "SIGKILL");
42741
+ if (!await waitGone(targets, killWaitMs))
42742
+ throw new Error(
42743
+ `jcode connector: a previous lifecycle's Jcode processes survived teardown (pids ${targets.filter(alive).join(", ")}) \u2014 this seat's runtime directory is still held`
42744
+ );
42745
+ }
42746
+ return targets;
42747
+ }
42707
42748
  async function stopPrivateTree(options) {
42708
42749
  const { jcodeHome, launch, identityValue, gracefulWaitMs = 3e3, killWaitMs = 2e3, settleMs = 500 } = options;
42709
42750
  const owned = /* @__PURE__ */ new Map();
@@ -59035,7 +59076,7 @@ config(en_default());
59035
59076
 
59036
59077
  // ../connector-core/dist/docs-bundle.generated.js
59037
59078
  var DOCS_BUNDLE = {
59038
- "version": "0.39.1",
59079
+ "version": "0.40.0",
59039
59080
  "generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
59040
59081
  "pages": [
59041
59082
  {
@@ -59141,7 +59182,7 @@ var DOCS_BUNDLE = {
59141
59182
  "title": "Connect Jcode (beta)",
59142
59183
  "kind": "Guide (informative)",
59143
59184
  "summary": "Jcode joins a Cotal mesh as a lateral peer.",
59144
- "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
59185
+ "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nA seat that dies without that teardown, from a manager restart or a kill past the grace window,\nleaves its Jcode server running. The server has a process group of its own and carries no\n`COTAL_NAME`, so a name-keyed reap does not reach it, and it holds the seat's runtime directory\nuntil its own five-minute idle timer expires. Each launch records its identity nonce and its host\nprocess in the private home, and the seat's next launch stops the tree that record names. The\nrecorded host is the gate: while it is still alive the seat is still serving, so nothing is\nsignalled and the second launch meets Jcode's own runtime-directory lock instead.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported. The short\nsocket alias the connector derives from that home is reclaimed at every launch, so a name a stopped\nseat used stays launchable.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model. `private_state` names a different step: the seat's private home, its\ncredential mirror, or its short socket alias could not be prepared.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
59145
59186
  },
59146
59187
  {
59147
59188
  "slug": "connect-opencode",
@@ -60634,7 +60675,7 @@ function writeMcpConfig(home, relay, config2) {
60634
60675
  throw new Error(`refusing to write Jcode MCP config at ${path4}: ${error51.message}`);
60635
60676
  }
60636
60677
  try {
60637
- writeFileSync3(fd, JSON.stringify(mcp));
60678
+ writeFileSync4(fd, JSON.stringify(mcp));
60638
60679
  } finally {
60639
60680
  closeSync2(fd);
60640
60681
  }
@@ -60729,8 +60770,22 @@ async function runJcodeHost() {
60729
60770
  assertNoProjectMcpConfig(cwd);
60730
60771
  const home = privateAgentHome(config2.space, config2.name);
60731
60772
  installJcodeDiagnosticLog(home);
60732
- mirrorJcodeCredentials(home);
60733
- const socketHome = shortSocketHome(home);
60773
+ try {
60774
+ const stopped = await stopOrphanedTree({ home });
60775
+ if (stopped.length)
60776
+ writeJcodeDiagnostic(`[cotal-jcode] stopped ${stopped.length} Jcode process(es) left running by a previous lifecycle of this seat
60777
+ `);
60778
+ } catch (error51) {
60779
+ writeJcodeDiagnostic(`[cotal-jcode] ${error51.message}
60780
+ `);
60781
+ }
60782
+ let socketHome;
60783
+ try {
60784
+ mirrorJcodeCredentials(home);
60785
+ socketHome = shortSocketHome(home);
60786
+ } catch (error51) {
60787
+ throw new JcodeConnectorError("private_state", "jcode connector: the seat's private Jcode state could not be prepared", { cause: error51 });
60788
+ }
60734
60789
  const relay = relayEndpoint(config2.space, config2.name);
60735
60790
  scrubLaunchMaterial();
60736
60791
  for (const key of Object.keys(process.env)) if (key.startsWith("COTAL_")) delete process.env[key];
@@ -60766,6 +60821,7 @@ async function runJcodeHost() {
60766
60821
  const exitListenersBefore = new Set(process.listeners("exit"));
60767
60822
  const launchBound = launchIdentityEnv();
60768
60823
  launchIdentityValue = launchBound.value;
60824
+ recordLaunch(home, { identity: launchBound.value, host: captureProcessIdentity(process.pid) });
60769
60825
  instance = await launchInstance({
60770
60826
  binary,
60771
60827
  jcodeHome: socketHome.jcodeHome,
@@ -61209,7 +61265,8 @@ var STARTUP_FAILURE_CODES = /* @__PURE__ */ new Set([
61209
61265
  "unsupported_version",
61210
61266
  "model_prefix_rejected",
61211
61267
  "model_refused",
61212
- "model_mismatch"
61268
+ "model_mismatch",
61269
+ "private_state"
61213
61270
  ]);
61214
61271
  function startupFailureCode(error51) {
61215
61272
  if (error51 instanceof Error && error51.message.startsWith("jcode connector: project MCP configuration")) return "project_mcp_config";
package/dist/index.js CHANGED
@@ -14840,7 +14840,7 @@ import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAll
14840
14840
 
14841
14841
  // ../connector-core/dist/docs-bundle.generated.js
14842
14842
  var DOCS_BUNDLE = {
14843
- "version": "0.39.1",
14843
+ "version": "0.40.0",
14844
14844
  "generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
14845
14845
  "pages": [
14846
14846
  {
@@ -14946,7 +14946,7 @@ var DOCS_BUNDLE = {
14946
14946
  "title": "Connect Jcode (beta)",
14947
14947
  "kind": "Guide (informative)",
14948
14948
  "summary": "Jcode joins a Cotal mesh as a lateral peer.",
14949
- "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
14949
+ "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nA seat that dies without that teardown, from a manager restart or a kill past the grace window,\nleaves its Jcode server running. The server has a process group of its own and carries no\n`COTAL_NAME`, so a name-keyed reap does not reach it, and it holds the seat's runtime directory\nuntil its own five-minute idle timer expires. Each launch records its identity nonce and its host\nprocess in the private home, and the seat's next launch stops the tree that record names. The\nrecorded host is the gate: while it is still alive the seat is still serving, so nothing is\nsignalled and the second launch meets Jcode's own runtime-directory lock instead.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported. The short\nsocket alias the connector derives from that home is reclaimed at every launch, so a name a stopped\nseat used stays launchable.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model. `private_state` names a different step: the seat's private home, its\ncredential mirror, or its short socket alias could not be prepared.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
14950
14950
  },
14951
14951
  {
14952
14952
  "slug": "connect-opencode",
package/dist/mcp.js CHANGED
@@ -57487,7 +57487,7 @@ import { execFileSync } from "node:child_process";
57487
57487
 
57488
57488
  // ../connector-core/dist/docs-bundle.generated.js
57489
57489
  var DOCS_BUNDLE = {
57490
- "version": "0.39.1",
57490
+ "version": "0.40.0",
57491
57491
  "generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
57492
57492
  "pages": [
57493
57493
  {
@@ -57593,7 +57593,7 @@ var DOCS_BUNDLE = {
57593
57593
  "title": "Connect Jcode (beta)",
57594
57594
  "kind": "Guide (informative)",
57595
57595
  "summary": "Jcode joins a Cotal mesh as a lateral peer.",
57596
- "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
57596
+ "body": "# Connect Jcode (beta)\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\n[Jcode](https://github.com/1jehuang/jcode) joins a Cotal mesh as a lateral peer. The connector\ncreates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes\nthe normal `cotal_*` tool surface through Jcode's documented stdio MCP configuration.\n\n**Beta** means the supported path is deliberately narrow: a fresh private session, prompt\ninjection, presence, managed start/stop, requested reasoning effort, and an attached TUI work.\nFeatures that do not preserve that private session's mesh surface fail loud: `--resume`,\nexact-session continuation, `--share-tools`, `--events`, and connector `--opt` values are not\nsupported.\n\n## Install\n\nThe connector is seeded with the Cotal CLI. Jcode's released Harness API bridge is a Unix-socket\nsurface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs.\nInstall Jcode 0.78.1 or later from its GitHub release and make the binary available as `jcode` on `PATH`:\n\n```bash\njcode version --json\ncotal spawn --agent jcode\n```\n\nIf an older Cotal installation is missing the connector, run `cotal ext seed --repair` (or\n`cotal ext add @cotal-ai/connector-jcode`). This connector intentionally uses the released\nbinary's `api-bridge` command; it does not require a Rust checkout.\n\n## Spawn it\n\n```bash\ncotal spawn --agent jcode\ncotal spawn reviewer --agent jcode -d\ncotal spawn --agent jcode --model gpt-5.6-sol --prompt \"Review the current change.\"\nCOTAL_DEFAULT_AGENT=jcode cotal spawn\n```\n\nA detached seat is managed normally: `cotal ps`, `cotal attach`, and `cotal stop` control the\nsame process the connector starts. In a terminal, Jcode opens on the managed session. With piped\noutput it stays headless; set `COTAL_JCODE_TUI=1` or `COTAL_JCODE_TUI=0` in the environment of the\nprocess building the launch to override that choice. For a detached spawn, that is the manager's\nenvironment.\n\n## How it binds\n\nJcode's stable integration surface is the **Harness API**: protocol-v1 NDJSON over a Unix socket.\nThe connector launches a **private instance** with `@1jehuang/jcode-sdk`'s `launchInstance()` and\nattaches only to that instance's own socket:\n\n- `launchInstance()` starts a private `JCODE_HOME`, runtime directory, daemon, and `api-bridge`;\n the connector holds the process handle first-hand and closes that instance with the Cotal seat.\n This gives each managed Cotal peer one owned session and prevents it from seeing or changing\n the operator's live Jcode sessions.\n- Attaching to an **operator-run** `jcode api-bridge` shares the operator's live session\n inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal\n seat: stop, prompt injection, and session selection could act on the operator's work. The\n connector never attaches to an operator bridge.\n- A managed seat **never updates its own binary**. Jcode's background updater restarts the\n process tree when it lands a release; that restart drops the seat's TUI, which is the only\n connection the Jcode server counts as a client, and nothing re-attaches, so the server's idle\n reaper takes the seat down five minutes later in the middle of a turn. The seat's version is\n whatever is on `PATH` when you spawn it, and it stays that version for the seat's life. Update\n deliberately, between seats, not under a running agent.\n\nOn a graceful stop **and** on a startup failure, the connector proves the private daemon tree is\nactually gone rather than trusting the SDK's registry-keyed stop (which is a silent no-op when the\n`servers.json` socket path does not match verbatim): it reads the PIDs the private home itself\nrecords, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a\nfailed stop instead of a clean one if any recorded process survives. It never signals by name, so\nteardown can only ever reach the seat's own tree.\n\nA seat that dies without that teardown, from a manager restart or a kill past the grace window,\nleaves its Jcode server running. The server has a process group of its own and carries no\n`COTAL_NAME`, so a name-keyed reap does not reach it, and it holds the seat's runtime directory\nuntil its own five-minute idle timer expires. Each launch records its identity nonce and its host\nprocess in the private home, and the seat's next launch stops the tree that record names. The\nrecorded host is the gate: while it is still alive the seat is still serving, so nothing is\nsignalled and the second launch meets Jcode's own runtime-directory lock instead.\n\nThe private Jcode home lives under `<manager-workspace>/.cotal/jcode/`. It is unique per\nspace/name and is owner-only. Jcode's own credential inheritance is used for the private instance,\nso provider logins work without copying its transcript/config tree into the seat. The spawned\nJcode process does not inherit `COTAL_*` values or the Cotal launch-material pointer.\n\nBecause the home is keyed by space and name, a seat respawned under the same space, name, and\nmanager workspace lands in the same home, and the connector automatically continues the\nnon-archived Jcode session there that was recorded for the seat's working directory and holds the\nlargest transcript, since that is the session carrying the memory a restart would otherwise throw\naway. A seat spawned under a fresh name keys a different home and starts with an\nempty transcript, so keep the same name when you want a replacement seat to continue where the\nprevious one stopped. This automatic continuation is a relaunch of the seat's own private session;\nit is separate from `--resume`, which names an outside session and stays unsupported. The short\nsocket alias the connector derives from that home is reclaimed at every launch, so a name a stopped\nseat used stays launchable.\n\nConnector diagnostics are written both to the spawning terminal and to an owner-only\n`<private-home>/logs/connector-<timestamp>-<pid>.log`, so a failed launch remains inspectable after\nthe manager's launch error scrolls away. Public startup failures stay scrubbed to allow-listed\ncodes rather than arbitrary Harness API messages.\n\nCredential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the\nallowlisted Jcode, provider-config, and external-login destinations, and removes a destination when\nits source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP\nconfiguration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the\nparents with `O_NOFOLLOW`, then publish, create, or unlink the leaf through the pinned parent rather\nthan through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write,\ncreate, or delete a namesake outside the private home.\n\nTwo mechanisms provide that pin. On Linux the leaf is named `/dev/fd/<fd>/<name>`, the openat and\nunlinkat equivalent Node does not expose, after `/dev/fd/<fd>/.` is proven to traverse. macOS mounts\n`/dev/fd` but has no subpath namespace under a descriptor, so there the connector pins the parent as\nthe process working directory instead: a single-component name resolves from that directory's inode\nand no ancestor is walked again. Entering by path is verified rather than trusted, because `chdir`\ntakes a path: the entered directory's inode must equal the inode of the descriptor opened a moment\nearlier, and a mismatch is refused by name. The working directory is restored on every exit,\nincluding the refusing ones. If neither pin is available, the connector throws a named error and\nmirrors nothing. Once a pin holds, `ENOENT` on a child means the mirror path is absent.\n\nThere is no credential-free opt-out today because the private instance must reproduce the operator's\ncurrent provider-login state rather than silently start with stale or partial authorization.\n\nIf a provider failure closes the private Harness API connection during a mesh-driven turn, the\nconnector leaves that turn's inbox batch unacknowledged and opens one bounded recovery window for a\nprivate replacement connection to the same session. A transient launch or attach failure retries\ninside that window, so a loaded host gets the same result as a fast one without creating an\nunbounded connector relaunch loop. The seat reports `waiting` while it reconnects, then redrives\nthat unacknowledged batch only after the session attaches. Each failed replacement must be proven\nstopped before another launch. A permanent Harness refusal, including an invalid request, missing\nsession, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately;\nanother launch cannot change it. An unprovable teardown, the recovery window expiring, or a second\ndisconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error\ncode remains transient by default and retries inside the same bounded window; new permanent codes\nmust be added to the explicit classifier and its exact-count regression.\n\nJcode currently supports **stdio** MCP servers. The connector writes only its own `cotal` entry to\nthe private `JCODE_HOME/mcp.json`; it starts a stdio MCP bridge for that entry and relays its calls\nto the host's one `MeshAgent`. The Jcode/MCP child receives a per-launch relay capability, but not\nthe Cotal broker credential or its launch-material pointer. Jcode also overlays project\n`.jcode/mcp.json`, `.mcp.json`, and `.claude/mcp.json`; a managed launch **refuses** a workspace\ncontaining any of those files, because one could replace the `cotal` bridge or add tools that were\nnot explicitly shared. Operator MCP configuration is isolated in the private home and project MCP\nconfiguration is not supported yet.\n\nBefore the seat joins the mesh, the host runs a mandatory Jcode turn that calls\n`cotal_orientation`. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool\nsnapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once\nin that case. A second absence fails the launch, so a bridge that never comes up remains a loud\nfailure rather than an agent that is present but mute. A managed Jcode seat has a **three-minute\nbounded readiness window**: first boot can download model material, start the MCP bridge, and wait\nthrough the provider-backed readiness turns. If that window expires, the launch is `uncertain`, not\na failed or cleanup verdict; use `cotal attach <name>` or `cotal ps` to inspect it and do not stop\nit solely because the window elapsed. The host then waits for the mesh connection and presence bind\nto complete before it adds a no-reply notice that the bootstrap orientation predates the join and\nthat a new orientation is live context. During a broker outage, it stays waiting and sends no\nconnected notice.\n\nFor a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn,\nso it streams boot activity instead of leaving the terminal blank. Presence still begins only after\nthe readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message\nthat arrives while the Harness session is busy (a Cotal-owned `run()`, a TUI-owned turn, or an\nadvisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode's session-owned\nsoft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks\npresence working while the session is busy, publishes `activity` naming automatic queue depth and age\nwhile anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only\nafter that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves\nthose ids unacknowledged for mesh redelivery. `cotal_inbox` pulls only buffered quiet\nambient from that host-owned queue; its shared optional `peek` argument is supported, so `peek: true`\nshows those messages without clearing them.\n\n## Model limits\n\n`--model` is passed to Jcode's session-level Harness API model selector. Jcode validates the model\nagainst the active provider, then the connector reads runtime identity back and refuses startup if\nit is not the requested model; a seat is never allowed to join under a model label it did not\nreceive.\n\nModel startup refusals are named without exposing provider output: `model_prefix_rejected` means a\n`provider/model` value was supplied where the Harness API requires a bare id, `model_refused` means\nJcode rejected that bare id, and `model_mismatch` means Jcode accepted the request but reported a\ndifferent effective model. `private_state` names a different step: the seat's private home, its\ncredential mirror, or its short socket alias could not be prepared.\n\n`cotal models --agent jcode` reads the declared catalog from the operator Jcode home's\n`config.toml`: each provider with `model_catalog = true`, its `[[providers.<name>.models]]` ids,\nand any declared `reasoning_efforts`. This is the same config Jcode copies into a private managed\ninstance. The command fails loud when the file is unreadable, malformed, or enables a catalog\nwithout model entries.\n\nThe listed effort tiers are declarations, not provider-verified capabilities. `cotal models` prints\nthat caveat inline as `variants (declared, not provider-verified)` beside each configured tier list,\nso it cannot be missed by reading only the model rows. Providers can reject a tier the file names,\nso launch remains the authority: Jcode applies the requested value and a provider rejection ends the\nlaunch. `--refresh` does not turn this local declaration into a live probe.\n\nThe Harness API can set a requested effort but cannot read an effective effort back. Its runtime\nidentity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal\ntherefore records the accepted request and does not relabel it as an observed effect.\n\n`--variant` is the session's **reasoning effort**, applied after the model and before the seat's\nfirst turn, so a seat never serves a turn at an effort nobody chose. A persona's `variant:` is the\ndefault and `--variant` overrides it, the same way `model:` and `--model` work:\n\n```bash\ncotal spawn --agent jcode --model gpt-5.6-sol --variant high\n```\n\nWhich tiers exist depends on the provider **and** model. The connector does not carry a copy of\nthose ladders: it passes the requested tier to Jcode, which validates it against the active model's\nladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than\nquietly starting the seat at another effort. The external observer/UI receives only the requested\ntier, effective model, fixed `invalid_request` provider code, and an accepted-tier ladder when it\ncan be safely parsed; arbitrary provider rejection text stays private. Omit `--variant` to keep\nJcode's configured default.\n\nIf the mandatory readiness turn receives a provider `invalid_request` refusal for a model id or\nreasoning-effort value, the launch diagnostic names only the provider error code and rejected\nvalue. Other provider response text remains scrubbed, so an external observer/UI can correct\nconnector-visible input without exposing private harness output.\n\nThe following fail loud before a new session is provisioned where the manager can preflight them,\nor at connector launch as a backstop:\n\n- **Resume /continuation:** a Cotal seat owns a new private Jcode instance. Reusing a session from\n an operator or another seat would violate that ownership boundary.\n- **Tool sharing:** Jcode resolves its MCP configuration from several global and project sources.\n The connector owns a private configuration containing only `cotal`, rather than claim a chosen\n subset can be safely merged.\n- **Events:** Jcode's Harness API does not provide the durable structured rollout surface required\n by Cotal's event plane.\n- **Launch options:** the connector does not map arbitrary flags/config into the Harness API.\n- **Containers:** the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.\n\n## Security limits\n\nThe private home protects against accidental sharing and stale session selection; it is not an\nOS-user isolation boundary. A hostile process running as the same user can still read that user's\nfiles or inspect another same-user process. Use OS/container isolation where peers must be mutually\nhostile.\n\nThe model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its\nprovider credentials, filesystem access, and network capability as the privileges of the OS user\nrunning the seat. Cotal's spawn capability governs who may create a seat; it is not a sandbox for\nwhat a model can be persuaded to do after creation.\n"
57597
57597
  },
57598
57598
  {
57599
57599
  "slug": "connect-opencode",
@@ -15,6 +15,44 @@ interface ProcessIdentityProbe {
15
15
  export declare const processHasLaunchIdentityForTest: (pid: number, identityValue: string, probe: ProcessIdentityProbe) => boolean;
16
16
  /** Every PID this seat's private home records. The caller must still prove ownership. */
17
17
  export declare function recordedTreePids(jcodeHome: string): number[];
18
+ export interface LaunchRecord {
19
+ /** The launch-bound environment identity only this launch's Jcode descendants carry. */
20
+ identity: string;
21
+ /** Immutable identity of the connector host that owns those descendants. */
22
+ host: ProcessIdentity;
23
+ }
24
+ /** Name this launch's tree in the seat home, so the seat's NEXT launch can tell a tree an already
25
+ * dead lifecycle left behind from one a live seat still legitimately holds. */
26
+ export declare function recordLaunch(home: string, record: LaunchRecord): void;
27
+ export declare function readLaunchRecord(home: string): LaunchRecord | undefined;
28
+ export interface StopOrphanedTreeOptions {
29
+ home: string;
30
+ gracefulWaitMs?: number;
31
+ killWaitMs?: number;
32
+ }
33
+ /**
34
+ * Stop the Jcode tree a previous lifecycle of this seat home left running, and return its PIDs.
35
+ *
36
+ * A seat that dies without its connector's teardown — a manager restart, a SIGKILL past the grace
37
+ * window — leaves its Jcode server alive. The server setsids into a group of its own and carries no
38
+ * `COTAL_NAME`, so neither the manager's signal nor a name-keyed reap reaches it, and it holds the
39
+ * seat home's runtime dir until its own five-minute idle timer (#1211).
40
+ *
41
+ * The recorded host PID is what keeps this from stopping a seat that is still serving, and it only
42
+ * releases the nonce once that host is provably GONE. A PID that is still alive at the recorded
43
+ * slot is ambiguous: it is either the connector that owns this home, or an unrelated process that
44
+ * reused the number, and nothing on a live PID distinguishes them reliably. This mechanism's whole
45
+ * claim is that it never signals a live seat's tree, so the ambiguous case resolves toward doing
46
+ * nothing. Pairing liveness with the captured start token instead would resolve it toward killing:
47
+ * a live host whose recorded token does not match would have its tree signalled while the host
48
+ * itself was left running, which is the two-seats-under-one-name failure this exists to avoid.
49
+ *
50
+ * The conservative branch costs one missed reap, after which Jcode's own runtime-dir lock gives its
51
+ * honest refusal and the next launch tries again. The permissive branch costs a seat that is still
52
+ * serving. Only a PID that is gone releases the record's launch nonce, and one nonce is inherited
53
+ * by one launch's descendants alone.
54
+ */
55
+ export declare function stopOrphanedTree(options: StopOrphanedTreeOptions): Promise<number[]>;
18
56
  export interface StopPrivateTreeOptions {
19
57
  jcodeHome: string;
20
58
  /** Immutable identity of the bridge child spawned by this launch. */
@@ -1 +1 @@
1
- {"version":3,"file":"private-lifecycle.d.ts","sourceRoot":"","sources":["../src/private-lifecycle.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAiCD,wBAAgB,iBAAiB,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAElE;AAED,mGAAmG;AACnG,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAMnE;AAoBD,UAAU,oBAAoB;IAC5B,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AA0CD,eAAO,MAAM,+BAA+B,GAC1C,KAAK,MAAM,EACX,eAAe,MAAM,EACrB,OAAO,oBAAoB,KAC1B,OAAoE,CAAC;AAkBxE,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB5D;AA0DD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,MAAM,EAAE,eAAe,CAAC;IACxB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;8FAE8F;AAC9F,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEpF"}
1
+ {"version":3,"file":"private-lifecycle.d.ts","sourceRoot":"","sources":["../src/private-lifecycle.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAiCD,wBAAgB,iBAAiB,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAElE;AAED,mGAAmG;AACnG,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAMnE;AAoBD,UAAU,oBAAoB;IAC5B,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AA0CD,eAAO,MAAM,+BAA+B,GAC1C,KAAK,MAAM,EACX,eAAe,MAAM,EACrB,OAAO,oBAAoB,KAC1B,OAAoE,CAAC;AAkBxE,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB5D;AA4DD,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,IAAI,EAAE,eAAe,CAAC;CACvB;AAED;+EAC+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAOrE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAYvE;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkB1F;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,MAAM,EAAE,eAAe,CAAC;IACxB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;8FAE8F;AAC9F,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"private-state.d.ts","sourceRoot":"","sources":["../src/private-state.ts"],"names":[],"mappings":"AAgFA;gGACgG;AAChG,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,cAAsB,EAAE,YAAoB,EAAE,YAAY,EAAE,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GACnJ,IAAI,CA6BN;AA6OD;;;;cAIc;AACd,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAyCpH;AAED;0GAC0G;AAC1G,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAiD9H;AAED;;sDAEsD;AACtD,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,IAAI,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAe3J;AAWD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,GAAG,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAMD;;oCAEoC;AACpC,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,qBAAqB,EAAE,CA6BhH;AAED;;4CAE4C;AAC5C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,oBAAsB,GAAG,IAAI,CAIxF;AAED,MAAM,WAAW,eAAe;IAC9B,gHAAgH;IAChH,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAqC7D"}
1
+ {"version":3,"file":"private-state.d.ts","sourceRoot":"","sources":["../src/private-state.ts"],"names":[],"mappings":"AAgFA;gGACgG;AAChG,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,cAAsB,EAAE,YAAoB,EAAE,YAAY,EAAE,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GACnJ,IAAI,CA6BN;AA6OD;;;;cAIc;AACd,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAyCpH;AAED;0GAC0G;AAC1G,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAiD9H;AAED;;sDAEsD;AACtD,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,IAAI,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAe3J;AAWD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,GAAG,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAMD;;oCAEoC;AACpC,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,qBAAqB,EAAE,CA6BhH;AAED;;4CAE4C;AAC5C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,oBAAsB,GAAG,IAAI,CAIxF;AAED,MAAM,WAAW,eAAe;IAC9B,gHAAgH;IAChH,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,IAAI,IAAI,CAAC;CACjB;AAwBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CA+B7D"}
@@ -1,4 +1,4 @@
1
- export type JcodeConnectorFailureCode = "model_prefix_rejected" | "model_refused" | "model_mismatch";
1
+ export type JcodeConnectorFailureCode = "model_prefix_rejected" | "model_refused" | "model_mismatch" | "private_state";
2
2
  /** A bounded connector-owned startup refusal. Only its allow-listed code is rendered publicly. */
3
3
  export declare class JcodeConnectorError extends Error {
4
4
  readonly code: JcodeConnectorFailureCode;
@@ -1 +1 @@
1
- {"version":3,"file":"startup-diagnostics.d.ts","sourceRoot":"","sources":["../src/startup-diagnostics.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAErG,kGAAkG;AAClG,qBAAa,mBAAoB,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,yBAAyB;gBAA/B,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAG9F;AAID,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB9D;AAED,iGAAiG;AACjG,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAG1D;AAED;;mGAEmG;AACnG,qBAAa,6BAA8B,SAAQ,KAAK;IAEpD,QAAQ,CAAC,YAAY,EAAE,MAAM;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,kBAAkB;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,GAAG,kBAAkB,EACvC,KAAK,EAAE,MAAM;CAIzB;AAED;;4CAE4C;AAC5C,qBAAa,kBAAmB,SAAQ,KAAK;IAIzC,QAAQ,CAAC,aAAa,EAAE,MAAM;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE;IAL5C,QAAQ,CAAC,YAAY,qBAAqB;gBAG/B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,SAAS,MAAM,EAAE;CAI7C;AAsDD;wFACwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,kBAAkB,CAEpH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAErG;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,6BAA6B,GAAG,SAAS,CAM1G"}
1
+ {"version":3,"file":"startup-diagnostics.d.ts","sourceRoot":"","sources":["../src/startup-diagnostics.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,GAAG,eAAe,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEvH,kGAAkG;AAClG,qBAAa,mBAAoB,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,yBAAyB;gBAA/B,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAG9F;AAID,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB9D;AAED,iGAAiG;AACjG,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAG1D;AAED;;mGAEmG;AACnG,qBAAa,6BAA8B,SAAQ,KAAK;IAEpD,QAAQ,CAAC,YAAY,EAAE,MAAM;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,kBAAkB;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,GAAG,kBAAkB,EACvC,KAAK,EAAE,MAAM;CAIzB;AAED;;4CAE4C;AAC5C,qBAAa,kBAAmB,SAAQ,KAAK;IAIzC,QAAQ,CAAC,aAAa,EAAE,MAAM;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE;IAL5C,QAAQ,CAAC,YAAY,qBAAqB;gBAG/B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,SAAS,MAAM,EAAE;CAI7C;AAsDD;wFACwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,kBAAkB,CAEpH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAErG;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,6BAA6B,GAAG,SAAS,CAM1G"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/connector-jcode",
3
3
  "description": "Cotal connector for Jcode: a host-mode mesh peer driven through Jcode's Harness API bridge.",
4
- "version": "0.39.1",
4
+ "version": "0.40.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,9 +29,9 @@
29
29
  "esbuild": "^0.28.0",
30
30
  "tsx": "^4.22.4",
31
31
  "zod": "^4.4.3",
32
+ "@cotal-ai/core": "0.40.0",
32
33
  "@cotal-ai/smoke-kit": "0.0.0",
33
- "@cotal-ai/connector-core": "0.39.1",
34
- "@cotal-ai/core": "0.39.1"
34
+ "@cotal-ai/connector-core": "0.40.0"
35
35
  },
36
36
  "files": [
37
37
  "dist"