Add install source infrastructure and package command support#2611
Merged
Conversation
Add design doc for unifying AppAgent install paths behind install sources (path/catalog/feed) and a single installed-agent provider backed by agents.json, with an InstallSourceRegistry doing ordered first-match resolution. Covers @install/@update/@source command surfaces, post-install live registration and uninstall teardown, feed enumeration/auth via Azure CLI tokens, first-run pre-install of bundled-catalog agents, one-time migration, and a decision log.
- clarify feed install location/config and host API organization in design - restructure execution plan into milestone-based units with full/light gates - add final branch-wide 2+2 gate and per-milestone commit requirements - add decisions log and deferred review/test-gap log with workflow hooks
Land the new install-source substrate while the legacy providers/installer still drive runtime — behavior-preserving, fully unit-tested (design §3, §4.1, §4.2). - dispatcher core: pure install-source interfaces in agentProvider/installSource.ts (InstallSourceKind, *SourceConfig, ResolvedCandidate, InstalledAgentRecord, InstallSource, InstallSourceRegistry, ExecutionMode string-union) exported from the barrel. No impl in core, no cycle to default-agent-provider (§4.1 layering). - default-agent-provider: bundled catalog data/agents.catalog.json mirroring config.json (every agent preinstall:true, §7/§12 Q1) + catalog loader; the 1.2 adapter routes only the default config through the catalog. - installSources/: path/catalog/feed sources + registry, in-process async mutex, feed auth (transient --userconfig, in-memory token, az login hint, §12 Q8), ~1h feed cache with offline/REST-error degrade (§12 Q3), keyword narrowing, ordered first-match resolve + explicit --source hard error (§12 Q4), execMode=separate for feed (§12 Q16), path omits module (§12 Q17). Decisions (see DECISIONS_LOG.md): default-config-only catalog adapter; ExecutionMode as a string union to avoid the node-providers cycle; corrupt user catalog degrades to non-match while a corrupt bundled catalog fails loud. Gate: 2 review + 2 test-gap rounds. Review fixed 2 majors (catalog corrupt degrade vs bundled fail-loud; feed cache-file name sanitization); 3 minors deferred with rationale (DEFERRED_REVIEW_LOG.md). Tests grown 39->66 specs; full package suite (3806) green, full build green, prettier clean. Behavior change for reviewers: none — the live install path is untouched.
Cut the default agent provider over from the static config `agents` map to an agents.json-backed installed-record model resolved through the M1 install sources (DESIGN.md §4.1, §5–§8). - installedAgents.ts: agents.json store; seedRecordsFromBundledCatalog (sync, no network) and seedRecordsFromConfig; legacy externalAgentsConfig.json migration (path-only, builtin-collision guard, rename .migrated); createInstalledAppAgentProvider with per-module-root grouping behind a combineAppAgentProviders facade (one AppAgentProvider, Q20); loadInstalled records lifecycle (in-memory / named / first-run seed+write / steady-state). - defaultAgentProviders.ts: getDefaultAppAgentProviders builds the single installed provider (signature unchanged); getDefaultAppAgentInstaller is registry-backed with a shared mutex and persists installSources/agents.json. - config.ts: installSources block (InstanceConfig.installSources, getResolvedInstallSources, seed order + dev workspace catalog, installDir). - agentProvider.ts: AppAgentInstaller now install(name, ref, sourceName?)/ uninstall/sources(); installCommandHandlers.ts: --source/--where flags, AGENT_NAME_RE + isAppAgentName pre-materialize validation (§ Q7/Q18). - agentServer/server.ts + api/webDispatcher.ts wire the agentInstaller. - Tests: installed store/seed/migrate/load, provider build/combine + installer (hermetic path-only config), resolved sources, and command-handler validation/--where/uninstall branches.
Add the additive `@update <name>` and `@source` command groups on top of the
Milestone 2 installed-record provider (design §5, §6, §4.7, §12 Q13).
- `@update <name> [range]` re-resolves an agent against its recorded source per
kind — feed bump (`module@range`), path re-materialize, catalog re-lookup —
overwriting `agents.json` only after the new version materializes, so a failed
update is a no-op (§4.7, Q13).
- `@source list|order|add {feed,catalog,path}|remove` manages the instance
`installSources` block via `installer.sources()`. `order` treats its args as a
priority prefix and appends the remaining configured sources (§5/§6); unknown
names warn and are skipped. `add catalog` distinguishes an unreadable file from
invalid JSON; `remove` warns and aborts when a source still backs installed
records unless `--force`.
- Layering: `@update` / `@source remove` reach record data through three optional
`AppAgentInstaller` methods (`update`, `sources`, `recordsUsingSource`) so the
dispatcher core never reads `agents.json` (see DECISIONS_LOG). `install`/`update`
preserve the re-resolution key in `record.ref` for renamed catalog/path agents.
Tests: installer update across kinds incl. changed-manifest re-materialize, no-op
on failure, ref-preservation, missing source/agent; `@source` list rendering,
order dedup/append/empty, add feed/catalog/path validation, remove referenced
guard. Full build green; defaultAgentProvider 3842 + dispatcher 1023 tests pass.
Make the feed-enumeration marker enforceable and retire the design draft status (design §4.1, §12 Q12). - New `agent-keyword` repo-policy rule (tools/scripts/policyChecks/agentKeyword.mjs): any package whose package.json exposes the `./agent/manifest` export — the exact subpath the npm agent provider loads — must carry `typeagent-agent` in its `keywords`, auto-fixed otherwise. Keys on the manifest export rather than the `@typeagent/agent-sdk` dependency to avoid over-matching infrastructure packages. - Added the keyword to all 36 agent packages (packages/agents/*, examples). The rule is enforced by `check:policy`, already wired into CI (azure-build-ts.yml). - Docs: documented `@install --source/--where`, `@source list|add|order`, and `@update` in the agent tutorial; flipped the design doc status to Implemented. The pre-M2 machinery (getDefaultNpmAppAgentProvider, getExternalAppAgentProvider, installNpm, isNpmSpecifier) is already removed; the externalAgentsConfig.json migration shim is intentionally retained for one release (see DECISIONS_LOG).
Final branch-wide gate over the whole installsource diff (review r1/r2 + test-gap r1/r2 against DESIGN.md §§1-12). - catalogSource.find now fails fast when a matched catalog entry carries neither `path` nor `name`, instead of producing a handle-less record that would only fail at load time (§4.2, §12 Q17). - test: registry sources + resolution order survive a reload from the persisted snapshot (§6 restart persistence, hermetic). - test: catalog malformed-entry (neither path nor name) fails fast (Q17). - test: remove drops the name from order(), not just list(). - docs: DECISIONS_LOG / DEFERRED_REVIEW_LOG / EXECUTION_PLAN final-gate outcome recorded. Full build green; defaultAgentProvider suite 3844 passing; repo policy 7954 checks passing.
The dispatcher core no longer knows the install-source kind taxonomy or the @source add grammar. InstallSource.kind is an opaque string; core exposes only InstallSourceInfo { name, kind, detail } for @source list, and the core @source table contributes list/order/remove. The host (default-agent-provider) now contributes the @source add subcommands: - AppAgentInstaller gains sourceCommands?(): CommandHandlerTable. - The system agent merges that table into the @source add node dynamically via getSystemHandlers(systemContext), used by getCommands/executeCommand/ getCommandCompletion and @help. - Host authors typed feed/catalog/path handlers in getAddSourceCommandHandlers(registry), each a real CommandHandler with typed args + flags and per-kind validation calling registry.add(config). - The kind taxonomy/config shapes moved to installSources/config.ts. DefaultInstallSourceRegistry keeps the typed add(config) for these handlers, seeding, and tests. Tests updated accordingly; DECISIONS_LOG records the final approach (superseding the earlier addSource(spec)/implicitQuotes and rest-arg explorations, both reverted).
Move the whole @source command (list/order/where/remove/add) to the host via AppAgentInstaller.sourceCommands(); the dispatcher core keeps only the live-session @install/@uninstall/@update handlers and merges the host table in as @source. Replace @install --where with host @source where <ref>. Also move the install-record types (ResolvedCandidate, InstalledAgentRecord) out of the core into the host (installSources/config.ts): nothing in the core consumed them. Delete core installSource.ts and drop the index.ts re-export; repoint all host importers to the local module. Update DESIGN.md to document the final layering. DECISIONS_LOG: fold the two 2026-06-27 entries into the design.
Compute the system command table (core commands plus the host's @source table) once per dispatcher and store it as CommandHandlerContext.systemCommands, since the installer is fixed for the dispatcher's lifetime. getSystemHandlers now takes the installer directly; systemAgent and the @help handler read the cached table instead of rebuilding it on every getCommands/getCommandCompletion/executeCommand call. Extract the executeCommand/getCommandCompletion bodies out of getCommandInterface into standalone executeCommandFromHandlers/getCommandCompletionFromHandlers so callers that already hold a table (systemAgent) can dispatch without allocating a wrapper interface; getCommandInterface now delegates to them. Tighten AppAgentCommandInterface.getCommandCompletion to take non-optional params: completion only runs after the dispatcher has parsed params, and the RPC contract and all callers already treat it as concrete. This removes a non-null assertion and an unreachable undefined guard.
…fault - Single ordered sources list IS the resolution order (first match wins); remove the redundant order() API and orderedSources() helper, merge the config/source maps into one entries map. - @source list/order/where reworked around the single list. - path source no longer defaults to process.cwd(): a relative ref without an explicit baseDir is a non-match (not a throw) so the ordered walk continues. - Add excludePathSources option (seed + persisted) so remote hosts drop path sources; web API server enables it. - Update DESIGN.md and config comments to match.
… of custom AsyncMutex
- Remove installDir from InstallSourcesConfig; it is now always <instanceDir>/installedAgents, derived at runtime and never persisted - Drop the process.cwd() fallback; in-memory case returns undefined - Split getResolvedInstallSources (sources) and getInstallDir (path) into separate functions since they share no inputs - Installer fails fast on an unresolved installDir before resolving sources - Update tests accordingly
Previously excludePathSources filtered path sources out of the list returned by getResolvedInstallSources, which both seeds the registry and gets persisted back to config.json on any @source edit. That would permanently drop path from the persisted/seeded list on the first mutation. Move the filter into the registry's resolution walk (and where()), and reject explicit --source for a path source on excluded hosts. The seeded and persisted source lists now always retain path; only runtime resolution skips it.
The workspace catalog source is seeded conditionally on the ambient filesystem and recomputed each launch until persisted, so a shared instance dir yields a different source list across dev/shipped contexts, and the first @source edit freezes a checkout-specific catalog path that goes stale elsewhere. Note this caveat in design 6 and above getSeedInstallSources; docs only, no behavior change.
Replace the internal Azure DevOps org/project (msctoproj/AI_Systems) with generic placeholders (myorg/myproject) in the install-source design doc example and the feed/registry test fixtures. The product feed name and @TypeAgent scope are retained; tests still pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 7, 2026
# Conflicts: # ts/packages/defaultAgentProvider/README.AUTOGEN.md # ts/pnpm-lock.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch replaces the older ad hoc package/source plumbing with a more explicit install-source model and a host-owned app-agent lifecycle. It adds install sources for bundled catalog entries, local paths, and feed-backed packages; moves
@package/@sourcebehavior into the host/provider boundary; and documents the resulting lifecycle, source model, and update-coordination design.What Changed
Install-source runtime in
defaultAgentProviderinstallSources/implementation for:defaultAgentProviders.tsaround installed-agent records and source-owned re-resolutionHost and dispatcher lifecycle changes
AppAgentHostand pushes app-agent/source lifecycle ownership closer to the host boundaryUpdate coordination and installed-agent behavior
@update <range>User-facing command surface
@package available@package list@source list@source add ...flows@packagecommandsDocumentation
Feed package eligibility
typeagent-agentkeyword, even if package-level feed enumeration matched earlierosconstraintUpdate - Complexity Baseline Exceptions
--exceptions-filetools/scripts/code/complexity-baseline-exception.jsonnpm run code-complexity:cifor local CI-parity ratchet runsnpm run code-complexity:update-exceptionsto regenerate the exception file from current complexity metrics