Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

agent-device runs as an optional module on workers

オープン
#597 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@V3RON がすでに取り組んでいます。

2026年10月15日 から。

  • #605 @V3RON による — オープン

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
8/100
issue の種類
リファクタリング
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
typescript
領域
backend, cli

調査の方向性

Start with src/daemon/main.ts, where agent-device is wired by static import on the worker path (lines 329-645), and src/core/config.ts, which holds its config type, validator and gateway warning. The change moves this into a new src/agent-device/definition.ts and worker.ts behind the module host from #595. Done means a worker with the module off never calls loadWorker, the old agentDevice key is rejected as unknown, and the listed tests pass under the new capabilities key.

索引モデルが issue の本文から書いたものです。

説明

task:ready

Part of #591.

Scope

Moves agent-device's worker side onto the module host from #595. After
this PR, agent-device is the registry's first module:

  • It is turned on with capabilities.agent-device.enabled. The top-level
    agentDevice key goes.
  • A worker with it turned off never loads its code.
  • src/daemon/main.ts no longer wires it on a worker.

Its behaviour is otherwise unchanged, with three exceptions:

  • With agent-device off, simlock no longer stops an agent-device daemon
    left by a crash (ADR 0030 §2).
  • On daemon stop, agent-device's two stop steps are now bounded by the
    Hook deadline;
    today they have no limit. Its slowest stop is about 22 s: a running
    --version check or reap (ps 5 s, SIGTERM 10 s, SIGKILL 2 s,
    F18). Past 30 s the drivers are disposed with agent-device still
    stopping, and the warning names it.
  • A gateway's warning for command uses the generic text (below).

The gateway side stays hand-wired in main.ts until #598. In this PR it
loads agent-device with a dynamic import(), only when it is turned on.
That way no daemon loads agent-device's code through a static import.

Test fixtures that build a whole Config lose their agentDevice line.
That edit is mechanical and is not counted in this task's size. Setup
lines that turn agent-device on (edge.test.ts, gateway-front.test.ts,
main.test.ts, worker-capabilities.e2e.test.ts,
lease-requires.e2e.test.ts) move to capabilities["agent-device"]
instead. The slow-lane files are rewritten (F14).

In short

An operator turns agent-device on under capabilities:

{ "capabilities": { "agent-device": { "enabled": true, "command": "agent-device", "leaseTtlCapMs": 60000 } } }

A config that still says { "agentDevice": { "enabled": true } } gets
Unknown config key: "agentDevice", and agent-device stays off. The
integration is unreleased, so nothing migrates the old key.

flowchart LR
  R["module-registry: agent-device *"] --> H[module host]
  H -->|on| L["loadWorker() → createWorker *"]
  H -->|off| O["agent-device: off<br/>no code loaded, no reap *"]

If this goes wrong: agent-device does not start on a worker that turns it
on, or simlock daemon stop leaves its process behind.

Technical spec

Facts
# Fact Source
F1 main.ts statically imports 15 names from ../agent-device/index.js. src/daemon/main.ts:5-21
F2 On a worker, main.ts builds agent-device's supervisor, edge parts, lease-end participant, capability source (with an off stub when disabled), relay target, HTTP front, stop steps and start. src/daemon/main.ts:329-357, :372, :484-488, :522-527, :548-551, :571-575, :592-596, :630-645
F3 agentDevice.stop() runs in dispose before the drivers, because the Android driver stops the adb server agent-device uses. src/daemon/main.ts:548-551
F4 stopAuxiliary disposes the edge and cancels waiting allocates through leasing.cancelPending. src/daemon/main.ts:571-575
F5 onSocketClaimed starts the supervisor, not awaited, unless a test passed agentDeviceUpstream. src/daemon/main.ts:592-596
F6 With config.enabled false, the supervisor starts in off; start() reaps a recorded daemon, then stays off. src/agent-device/host/agent-device-supervisor.ts:163, :255-286
F7 Two supervisor tests pin the off path. src/agent-device/host/agent-device-supervisor.test.ts:346, :1161
F8 The agent-device config is { enabled, command, leaseTtlCapMs? }, default { enabled: false, command: "agent-device" }, leaseTtlCapMs from 5000 to lease.maxTtlMs; command is worker-only. The core holds its type, the cap check and its call, its gateway key, its gateway warning, its default and its validator. The validators come from src/core/validation.ts, which src/core/index.ts does not export; invalidValue words an error Invalid config value for "<path>": expected <what>. src/core/config.ts:253-257, :410, :452-459, :485, :494, :522-530, :763, :916-920; src/core/validation.ts:17-19, :17-118
F9 Config tests for agent-device: one describe block of 8 tests, plus a gateway test. src/core/config.test.ts:1961-2036, :1776
F10 configSchema names agentDevice. src/contract/schemas.ts:902-906
F11 Tests pass agentDeviceUpstream or agentDeviceHealth to startDaemon. src/agent-device/edge.test.ts:165; src/agent-device/gateway-front.test.ts:270; src/daemon/main.test.ts:2430; src/daemon/worker-capabilities.e2e.test.ts:66; src/daemon/lease-requires.e2e.test.ts:89
F12 Five assertions name the old key: three in those suites, two in the supervisor's tests. src/agent-device/gateway-front.test.ts:703, :709-711; src/agent-device/edge.test.ts:2098-2105; src/agent-device/host/agent-device-supervisor.test.ts:421, :480
F13 The supervisor's remedies tell the user to set agentDevice.command. src/agent-device/host/agent-device-supervisor.ts:228-230
F14 The slow-lane tests write and delete agentDevice in a config. e2e/slow-agent-device.test.ts; e2e/slow-agent-device-gateway.test.ts:160, :173, :264, :280
F15 docs/CONFIGURATION.md documents the agentDevice.* keys, the gateway slice, and "A daemon a killed Simlock left behind is stopped at the next start". docs/CONFIGURATION.md:38-40, :162-169
F16 The gateway path reads config.agentDevice for enabled and leaseTtlCapMs. src/daemon/main.ts:827, :958-960
F17 dependency-cruiser records import() as dynamic-import, import type as type-only, and import("x").T as type-import. node_modules/dependency-cruiser/src/extract/tsc/extract-typescript-deps.mjs:82, :424, :436 (18.4.0)
F18 The supervisor's waits: ps 5 s, --version 10 s, SIGTERM grace 10 s, SIGKILL grace 2 s. src/agent-device/host/agent-device-supervisor.ts:76-80
F19 agent-device's front imports src/http's helpers and types. src/agent-device/front/front.ts:1; src/agent-device/front/allocate.ts:4-6; src/agent-device/host/host.ts:3
F20 Today a gateway warns Ignoring "agentDevice.command": it names the program a worker runs, and this daemon runs in gateway mode. src/core/config.ts:528
Modules touched
flowchart LR
  main["daemon/main.ts *"] --> host["modules host"]
  main --> reg["module-registry *"]
  reg --> def["agent-device/definition *new*"]
  def -. loadWorker .-> w["agent-device/worker *new*"]
  main -. "dynamic import (gateway, until #598)" .-> idx["agent-device/index *"]
  w --> sup["agent-device supervisor *"]
  cfg["core/config.ts, core/index.ts *"]
  contract["contract/schemas.ts, protocol.ts *"]
Surface and extension points

src/agent-device/definition.ts exports agentDeviceModule, an
OptionalModuleDefinition:

  • name: "agent-device".
  • config:
    • F8's validator without enabled, which the core owns (#595), built
      from validators that src/core/index.ts now exports
      (objectValidator, nonEmptyString, integerInRange,
      invalidValue);
    • F8's defaults without enabled;
    • gatewayKeys: ["leaseTtlCapMs"];
    • a check that leaseTtlCapMs is at most lease.maxTtlMs, failing
      with invalidValue's text,
      Invalid config value for "capabilities.agent-device.leaseTtlCapMs": expected at most lease.maxTtlMs.
  • Every agent-device piece F8 lists leaves src/core/config.ts.
  • loadWorker: () => import("./worker.js"). No loadGateway until #598.

src/agent-device/worker.ts exports createWorker(ctx). It builds the
supervisor and the edge parts from ctx (F2), and returns:

  • capability, from today's capability source;
  • relayTarget, from today's relay target;
  • mount, the HTTP front's handler (the front keeps using src/http's
    helpers, F19; ADR 0030 §6), with ctx.config.leaseTtlCapMs and
    maxRunMs = ctx.daemonConfig.lease.maxTtlMs;
  • findings, today's agentDeviceFindings returning
    { message, remedy }; the host now stamps module (#596's line moves
    here);
  • hooks.afterSocketClaimed: void supervisor.start() (F5);
  • hooks.beforeDeviceReclaim: the lease-end participant;
  • hooks.beforeDrain: dispose the edge, then cancel waiting allocates
    through ctx.cancelPending (F4);
  • hooks.beforeDriversDispose: supervisor.stop() (F3).

src/agent-device/testing.ts exports
agentDeviceModuleWith({ upstream?, health? }), a definition whose
createWorker uses the given fakes. With upstream, the supervisor is
never started, as F5 does today.

Other changes:

  • src/agent-device/index.ts keeps only what the gateway path uses
    (F16). main.ts's gateway path loads it with await import(...) only
    when capabilities.agent-device.enabled is true. It reads the section
    through the type definition.ts exports, and a missing section counts
    as off. Every other name F1 lists leaves main.ts.
  • StartDaemonOptions drops agentDeviceUpstream and agentDeviceHealth.
  • The supervisor drops its enabled branch (F6). It is built only for a
    turned-on module.
  • The supervisor's remedies say capabilities.agent-device.command (F13).
  • A gateway warns Ignoring "capabilities.agent-device.command": it configures a worker, and this daemon runs in gateway mode.,
    #595's generic text, in place of F20's.
  • New dependency-cruiser rule main-loads-modules-lazily: main.ts
    imports src/agent-device only as dynamic-import, type-only or
    type-import (F17). #598 tightens it to no import at all.
Contract and event changes

configSchema drops agentDevice (F10); agent-device's section travels in
capabilities. This PR bumps the protocol by one. No event changes.

Other code on the same state
  • The gateway path in main.ts (F16) reads agent-device's config. It
    switches to config.capabilities["agent-device"], with the same
    behaviour.
  • #595's host tests pin agent-device's hand-wired entry first in the
    capability list, and its stop steps before the hooks. With the hand
    wiring gone, those tests assert the registry order only, with
    agent-device as a registered module.
  • The core doctor gets only host.findings() at main.ts:329.
  • A leftover agent-device daemon while off: nothing acts on it any
    more. The next start with agent-device on reaps it, as today (F6).
Rules in play
  • architecture.md rules 18 and 21: main.ts names no module on the
    worker path.
  • safety.md: the reap's ownership proof is unchanged.
  • testing.md: delete only F7's two tests, whose path is gone. F9's tests
    move, they are not deleted.
  • documentation.md: F15, plus CLI.md, HTTP-API.md and CLIENT.md
    wherever they write agentDevice.
Tests

Seam:
startDaemon({ optionalModules: [agentDeviceModuleWith({ upstream: fake })], configOverrides: { capabilities: { "agent-device": … } } })
in place of agentDeviceUpstream in the suites F11 names, modelled on
src/agent-device/edge.test.ts. Those suites still pass, with their setup
changed and F12's three assertions renamed to the new key.

  • a worker with no capabilities["agent-device"] section answers status.get with { name: agent-device, state: off }, and so does one with enabled: false
  • a worker with agent-device off never calls agent-device's loadWorker (a spying definition that wraps agentDeviceModule)
  • a gateway with agent-device off, or with no section for it, mounts no /v1/agent-device (as today)
  • a worker whose config has only the old agentDevice key warns Unknown config key: "agentDevice" and reports agent-device off
  • a worker with agent-device off starts no supervisor: no ps runs and no signal is sent, even with a recorded daemon on disk
  • F9's tests move to src/agent-device/definition.test.ts, under capabilities.agent-device (as today):
    • the defaults, and an explicit section kept as written;
    • command refused when empty, and when a number;
    • leaseTtlCapMs accepted at 5000 and at lease.maxTtlMs, refused as a fraction, refused below 5000, and refused above lease.maxTtlMs with invalidValue's text naming capabilities.agent-device.leaseTtlCapMs;
    • a loaded config parsed through configSchema keeps the section;
    • on a gateway, enabled and leaseTtlCapMs read without a warning, and command warned about with the generic text
  • startDaemon with agent-device on stops the agent-device child before the drivers are disposed (as today, main.test.ts:2449)
  • with agent-device on, a release still waits for agent-device's lease end before the device is reclaimed (as today)
  • daemon stop cancels an agent-device allocate waiting in the queue before leasing settles (as today)
  • doctor.run reports { kind: "module-unavailable", module: "agent-device" }, with the module stamped by the host
  • the supervisor's not-found and not-agent-device remedies name capabilities.agent-device.command (F12's two supervisor assertions)
  • config.get shows agent-device's section under capabilities["agent-device"] and no agentDevice key
  • the conformance suite runs over agent-device as a registered module
  • dependency-cruiser fails a value import of src/agent-device from main.ts (a lint-harness case); pnpm lint passing on this PR's main.ts, with its dynamic and type imports, shows the allowed kinds pass
  • the protocol version is one above the base branch's

Done when

  • Every Tests line passes, and every other test in the suites F11 names
    passes with only its setup changed and F12's assertions renamed.
  • F7's two supervisor tests are deleted. No other agent-device test is
    deleted.
  • docs/CONFIGURATION.md documents capabilities.agent-device.*. Its
    leftover-daemon line says it applies with
    capabilities.agent-device.enabled true.
  • F14's config writes and deletes use capabilities["agent-device"]. On a
    Mac with an iOS runtime: scripts/slow-e2e.sh e2e/slow-agent-device.test.ts
    and scripts/slow-e2e.sh e2e/slow-agent-device-gateway.test.ts pass.

Out of scope

  • The gateway side moving onto the host (#598).
  • The doctor finding's shape (#596).

Depends on

  • #595

Written by an agent.

主要言語
TypeScript
スター
19
フォーク
1
平均マージ
7時間 57分
マージ済み PR(30日)
213

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

callstackincubator/simlock のほかの issue

callstackincubator/simlock の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。