services-extension-consumption
Skill1k starsChanged 27 days agoBSD-3-ClauseView on GitHub
Consume the salesforcedx-vscode-services extension API. Use when an extension depends on salesforcedx-vscode-services and you are registering commands, calling its services (Workspace, Connection, Project, Settings, FS, Channel, Media, prompts), watching files/config/target-org, or wiring the AllServicesLayer/runtime in extensionProvider.ts.
Adopted by
Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data
Who's talking about forcedotcom/salesforcedx-vscode
- prettier/prettier-vscode“Couldn't resolve parser "json-stringify"”GitHub · Jun 5, 2018 · 23 reactions
- Stack Overflow“VSCode / Salesforce - Deploy Source to Org very slow to complete, although actual deployment is fast?”Stack Overflow · Jun 26, 2023 · 4 votes
- xixiaofinland/sf.nvim“Debugging functionality”GitHub · Oct 1, 2024 · 3 reactions
- EclipseFdn/open-vsx.org“Claiming namespace salesforce”GitHub · May 3, 2023 · 2 reactions
- forcedotcom/salesforcedx-apex“fix: set correct exit codes on failure”GitHub · Apr 23, 2021 · 2 reactions
The biggest 5 of 52 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 52
Talked about on GitHub and Stack Overflow (52 times).
On GitHub · 50
- Jun 5, 2018Couldn't resolve parser "json-stringify" — prettier/prettier-vscode23 reactions · 6 comments
- Oct 1, 2024Debugging functionality — xixiaofinland/sf.nvim3 reactions · 14 comments
- May 3, 2023Claiming namespace salesforce — EclipseFdn/open-vsx.org2 reactions · 21 comments
- Apr 23, 2021fix: set correct exit codes on failure — forcedotcom/salesforcedx-apex2 reactions
- Sep 11, 2024E2E tests fail in VSCode version 1.93.0 with "target window already closed" when running "Developer: Reload Window" — webdriverio-community/wdio-vscode-service1 reaction · 11 comments
45 more on GitHub
- Sep 30, 2020Trigger completions on specific characters — forcedotcom/lightning-language-server1 reaction · 2 comments
- Jun 25, 2019Source commands do not display error information — forcedotcom/cli1 reaction · 34 comments
- Aug 27, 2018Inform extensions that call 'vscode.openFolder' with file URIs created with URI.parse — microsoft/vscode1 reaction
- Aug 3, 2017Show externalised strings in extension editor contributions tab — microsoft/vscode1 reaction · 8 comments
- Nov 13, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 8 updates — certinia/debug-log-analyzer0 reactions
- Nov 11, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.10.0 → 68.12.0 — sudosubin/agents.nix0 reactions
- Nov 11, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.10.0 → 68.12.0 — sudosubin/agents.nix0 reactions
- Nov 9, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 6 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Nov 6, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 5 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Nov 5, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.8.0 → 68.10.0 — sudosubin/agents.nix0 reactions
- Nov 5, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 4 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Nov 4, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.8.0 → 68.10.0 — sudosubin/agents.nix0 reactions
- Nov 4, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 2 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 29, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.6.1 → 68.8.0 — sudosubin/agents.nix0 reactions
- Oct 29, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.6.1 → 68.8.0 — sudosubin/agents.nix0 reactions
- Oct 27, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.6.0 → 68.6.1 — sudosubin/agents.nix0 reactions
- Oct 27, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.6.0 → 68.6.1 — sudosubin/agents.nix0 reactions
- Oct 27, 2026build(deps-dev): bump @salesforce/playwright-vscode-ext from 1.7.0 to 1.7.1 in the development-dependencies group across 1 directory — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 26, 2026build(deps-dev): bump @salesforce/playwright-vscode-ext from 1.7.0 to 1.7.1 in the development-dependencies group across 1 directory — certinia/debug-log-analyzer0 reactions · 2 comments
- Oct 22, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 10 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 21, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.4.0 → 68.6.0 — sudosubin/agents.nix0 reactions
- Oct 21, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.4.0 → 68.6.0 — sudosubin/agents.nix0 reactions
- Oct 21, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 8 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 19, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 7 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 16, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 68.2.0 → 68.4.0 — sudosubin/agents.nix0 reactions
- Oct 16, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 68.2.0 → 68.4.0 — sudosubin/agents.nix0 reactions
- Oct 16, 2026build(deps-dev): bump @salesforce/vscode-services from 67.20.0 to 67.27.5 — forcedotcom/apex-language-support0 reactions · 2 comments
- Oct 16, 2026feat(git): replace isomorphic-git with Effect Git - W-22711959 — forcedotcom/source-tracking0 reactions · 3 comments
- Oct 16, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 4 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 14, 2026build(deps-dev): bump the development-dependencies group across 1 directory with 2 updates — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 14, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: 67.26.0 → 68.2.0 — sudosubin/agents.nix0 reactions
- Oct 13, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 67.26.0 → 68.2.0 — sudosubin/agents.nix0 reactions
- Oct 12, 2026build(deps-dev): bump @salesforce/vscode-services from 67.26.0 to 67.27.1 in the development-dependencies group — certinia/debug-log-analyzer0 reactions · 1 comment
- Oct 4, 2026claude-code-plugins.github.forcedotcom.salesforcedx-vscode: init at 67.26.0 — sudosubin/agents.nix0 reactions
- Oct 3, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: 67.20.0 → 67.26.0 — sudosubin/agents.nix0 reactions
- Sep 27, 2026agent-skills.github.forcedotcom.salesforcedx-vscode: init at 67.20.0 — sudosubin/agents.nix0 reactions
- Sep 25, 2026build(deps-dev): bump @salesforce/vscode-services from 67.17.18 to 67.20.0 — forcedotcom/apex-language-support0 reactions · 1 comment
- Sep 18, 2026build(deps-dev): bump @salesforce/vscode-services from 67.15.0 to 67.17.4 — forcedotcom/apex-language-support0 reactions · 1 comment
- Aug 28, 2026build(deps-dev): bump @salesforce/vscode-services from 67.10.0 to 67.13.3 — forcedotcom/apex-language-support0 reactions
- Aug 24, 2026Use Org Metadata Catalog for find-missing-artifact org lookups - W-23971749 — forcedotcom/apex-language-support0 reactions · 3 comments
- Aug 21, 2026build(deps-dev): bump @salesforce/vscode-services from 67.7.1 to 67.10.0 — forcedotcom/apex-language-support0 reactions
- Aug 19, 2026feat: export stdValueSetRegistry.json from main index.ts to be called properly outside this repo - W-23802414 — forcedotcom/source-deploy-retrieve0 reactions
- Jul 24, 2026build(deps): bump @salesforce/vscode-i18n from 66.7.1 to 66.8.0 — forcedotcom/apex-language-support0 reactions
- Jul 8, 2026Support new SFDX: Create OpenAPI Document from This Class (Beta) — RoseSilverSoftware/IlluminatedCloud0 reactions
- Jul 8, 2026Agentforce for Developers inline autocompletion not working (temporary Salesforce API regression) — RoseSilverSoftware/IlluminatedCloud0 reactions
On Stack Overflow · 2
In this repository46 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .cursor/rules/doc-maintenance.mdcCursor rule
- .cursor/rules/verification.mdcCursor rule
- .cursor/rules/wireit.mdcCursor rule
- vscode-extension-launchSkill
- announce-prereleaseSkill
- backlog-groomingSkill
- changelogSkill
- command-uiSkill
- conciseSkill
- core-extension-apiSkill
- dependabot-alertsSkill
- drivable-vscodeSkill
- effect-best-practicesSkill
- external-consumersSkill
- feature-branchSkill
- grill-meSkill
- gus-cliSkill
- i18n-messagesSkill
- icons-srcSkill
- lit-componentsSkill
- lit-webview-architectureSkill
- lit-webview-testingSkill
- lsp-custom-requestsSkill
- merge-conflictsSkill
- packageJsonSkill
- patch-releaseSkill
- pathsSkill
- playwright-e2eSkill
- pr-draftSkill
- query-app-insightsSkill
- releaseSkill
- shipped-issuesSkill
- span-file-exportSkill
- thermo-nuclear-code-quality-reviewSkill
- ts1261-filename-casingSkill
- ts4023-effect-errorsSkill
- typescriptSkill
- verificationSkill
- vscode-elements-webviewsSkill
- vscode-window-messagesSkill
- web-console-local-tracesSkill
- wireitSkill
- work-item-sequencingSkill
- changelog-judgmentSkill
- manual-test-plan-judgmentSkill
The file
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 3 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 13
Use ExtensionProviderService from @salesforce/effect-ext-utils:
- Very specific: line 84
Do: Use getRuntime().runPromise(effect) / runFork(effect) for ad-hoc execution.
- Very specific: line 85
Don't: Use Effect.provide(AllServicesLayer) at call sites — use the runtime instead.
- Distinctive: line 102
Prefer Effect scope ownership for resources created inside Effect services/layers:
- Very specific: line 108
Don't expose runDispose/dispose solely for consumers to add to context.subscriptions.
- Very specific: line 109
Keep context.subscriptions for resources created outside an Effect scope.
- Very specific: line 143
Only then await connection resolution, target-org readiness, catalog hydration, or network work.
- Distinctive: line 201
No args: interactive flow (palette).
- Very specific: line 307
Call ClearDefaultOrgRef() to reset the in-process org ref (e.g., after deleting the default org):
- Very specific: line 394
Start with api.services.prebuiltServicesLayer — don't add individual *.Default for services already there
- Distinctive: line 395
Only add per-extension layers on top
- Very specific: line 402
Use getRuntime().runPromise / runFork instead of Effect.provide(AllServicesLayer) for execution
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Consuming salesforcedx-vscode-services
Extensions depending on salesforcedx-vscode-services. Examples: salesforcedx-vscode-metadata, salesforcedx-vscode-org-browser.
Getting the API
Use ExtensionProviderService from @salesforce/effect-ext-utils:
import { ExtensionProviderService, getServicesApi } from '@salesforce/effect-ext-utils';
const ExtensionProviderServiceLive = Layer.effect(
ExtensionProviderService,
Effect.sync(() => ({
getServicesApi
}))
);
// In an Effect.gen:
const api = yield * (yield * ExtensionProviderService).getServicesApi;Prebuilt vs Per-Extension Services
api.services.prebuiltServicesLayer — shared service instances plus redacting-logger FiberRef. Not the OTEL tracer. Provide or merge this layer directly.
api.services.prebuiltServicesDependencies — deprecated context-only field. Omits FiberRefs; use prebuiltServicesLayer.
Shares singleton instances (caches, watchers) across extensions; avoids re-building stateful services.
Per-extension layers (must build yourself):
| Layer | Why |
ChannelServiceLayer(displayName) | Own output channel; disposed when layer scope closes |
ErrorHandlerService.Default | Depends on own ChannelService |
ExtensionContextServiceLayer(context) | Own ExtensionContext |
SdkLayerFor(context) | Own tracer (extension name/version in resource attributes) |
ExtensionProviderServiceLive | Local singleton |
ExtensionContext Setup
Preferred: import buildAllServicesLayer from @salesforce/effect-ext-utils. It reads displayName from package.json, falling back to the second arg. services/extensionProvider.ts only needs the mutable AllServicesLayer + setter:
// services/extensionProvider.ts
import { buildAllServicesLayer } from '@salesforce/effect-ext-utils';
export let AllServicesLayer: ReturnType<typeof buildAllServicesLayer>;
export const setAllServicesLayer = (layer: ReturnType<typeof buildAllServicesLayer>) => {
AllServicesLayer = layer;
};In activate — pass the context and a localized fallback channel name:
import { buildAllServicesLayer } from '@salesforce/effect-ext-utils';
import { nls } from './messages';
import { setAllServicesLayer } from './services/extensionProvider';
export const activate = async (context: vscode.ExtensionContext): Promise<void> => {
setAllServicesLayer(buildAllServicesLayer(context, nls.localize('channel_name')));
await getRuntime().runPromise(activateEffect(context));
};Two patterns exist depending on whether the extension adds services beyond the shared base:
- Shared base only (
core,apex,apex-testing,lightning,lwc,org,visualforce): importbuildAllServicesLayerdirectly from@salesforce/effect-ext-utilsand pass it tosetAllServicesLayerat activation. No local factory needed. - Extension-specific services added (
apex-debugger,apex-log,apex-oas,apex-replay-debugger,metadata,org-browser,soql): define a localbuildAllServicesLayerinservices/extensionProvider.tsthat callsbuildSharedServicesLayerfrom@salesforce/effect-ext-utilsand merges the extension's own Effect services viaLayer.mergeAll. The extra services vary —apex-oasaddsApexMetadataServiceandLLMService; extensions with the notifications system addNotificationModeService.Default;org-browseraddsOrgBrowserRetrieveService.
Runtime vs provide
buildAllServicesLayermergessuppressVersionMismatchWarning. Extension code keepsManagedRuntime.make(AllServicesLayer); do not wrap it. The services activation runtime inlinesLayer.setVersionMismatchErrorLogLevel(Option.none())because it cannot import effect-ext-utils.- Do: Build
ManagedRuntime.make(AllServicesLayer)and exportgetRuntime(). - Do: Export runtime disposal, clear the memo, and call it during extension deactivation.
- Do: Use
getRuntime().runPromise(effect)/runFork(effect)for ad-hoc execution. - Don't: Use
Effect.provide(AllServicesLayer)at call sites — use the runtime instead.
export const disposeRuntime = async (): Promise<void> => {
if (_runtime) {
await _runtime.dispose();
_runtime = undefined;
}
};
export const deactivate = async (): Promise<void> => {
await getRuntime().runPromise(deactivation()).finally(disposeRuntime);
};Resource Lifecycle
Prefer Effect scope ownership for resources created inside Effect services/layers:
- Define resource-owning services with
scoped. - Register VS Code
Disposables withEffect.addFinalizer. - Attach long-lived fibers to the owning scope with
Effect.forkIn. - Dispose the owning
ManagedRuntimeon deactivation so layer finalizers run. - Don't expose
runDispose/disposesolely for consumers to add tocontext.subscriptions. - Keep
context.subscriptionsfor resources created outside an Effect scope.
Allocation and cleanup stay together. See ../effect-best-practices/SKILL.md#effect-owned-resources.
Registering Commands
Use registerCommandWithRuntime:
import { myCommandEffect } from './commands/myCommand';
const api = yield * (yield * ExtensionProviderService).getServicesApi;
const registerCommand = api.services.registerCommandWithRuntime(getRuntime());
yield * registerCommand('sf.my.command', myCommandEffect);Commands auto:
- Register with ExtensionContext subscriptions
- Wrap with error handling
- Trace with observability spans
- Handle Cancellation
Activation ordering
activate() awaits getRuntime().runPromise(activateEffect(context)); it does not detach the main activation Effect. Only work explicitly started with Effect.fork* continues after activation completes.
Register all manifest-contributed UI before awaiting work that can be slow or unresolved:
- Register tree/webview providers and put any returned
Disposableincontext.subscriptionswhen it is not scope-owned. - Restore the extension's persisted UI state and set its context keys.
- Register every contributed command.
- Set an extension-owned readiness context key only after steps 1-3 succeed, and use it to gate title/menu commands that would otherwise be visible.
- Only then await connection resolution, target-org readiness, catalog hydration, or network work. Use
Effect.forkInfor long-lived watchers that do not need to block activation.
when clauses can expose a contributed command before its handler has registered. A context key owned by another extension, including sf:has_target_org, is a visibility hint, not proof that this extension has initialized. Do not make a contributed handler's registration depend on it. Keep target-org and authorization checks in the command implementation or shared service layer.
export const activateEffect = Effect.fn(`activation:${EXTENSION_NAME}`)(function* (context: vscode.ExtensionContext) {
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const provider = new MyTreeProvider();
context.subscriptions.push(vscode.window.registerTreeDataProvider(VIEW_ID, provider));
yield* setInitialContext();
const registerCommand = api.services.registerCommandWithRuntime(getRuntime());
yield* registerCommand('sf.my.command', () => myCommand(provider));
yield* Effect.promise(() => vscode.commands.executeCommand('setContext', 'sf:myExtension.ready', true));
// Command registration must not wait for org-backed initialization.
yield* api.services.ConnectionService.getConnection();
});Success handling
Effect.fn middleware runs left to right after the generator. Effect.tap and *SuccessNotification before catch* (catch, catchAll, catchAllCause, catchCause, catchCauseIf, catchIf, catchSome, catchSomeCause, catchTag, catchTags) — a success combinator after catch treats recovery as success. Later catch valid. Later non-success guard (preventOrgChanges) valid. Lint: local/effect-fn-catch-middleware-last on spanned Effect.fn (fn('span'), fn('name', options)). Skips fnUntraced, unspanned fn(function*), generator-body catch, .pipe catch.
export const deployActiveEditorCommand = Effect.fn('deploySourcePath.deployActiveEditor')(
function* () {
// ...core logic...
},
// runs only on success — placed before catchTag
withConfigurableSuccessNotification(nls.localize('command_succeeded_text', label)),
// catches errors — placed after success middleware
Effect.catchTag('NoActiveEditorError', () =>
Effect.promise(() => vscode.window.showErrorMessage(nls.localize('deploy_select_file_or_directory'))).pipe(
Effect.as(undefined)
)
)
);withConfigurableSuccessNotification wraps the effect with Effect.tap, so it only fires when the effect succeeds:
export const withConfigurableSuccessNotification =
(message: string) =>
<A, E, R>(effect: Effect.Effect<A, E, R>) =>
Effect.tap(effect, () =>
Effect.sync(() => {
const show = vscode.workspace.getConfiguration(SECTION).get<boolean>(KEY, false);
if (show) void vscode.window.showInformationMessage(message);
})
);Invoking sf.org.login.web
Cross-extension / executeCommand: vscode.commands.executeCommand('sf.org.login.web', instanceUrl?, reauthAliasOrUsername?).
- No args: interactive flow (palette).
- With
instanceUrl: skips org-type quick pick. - Second arg applies only when
instanceUrlwas provided: trimmed non-empty string becomes the auth alias (access-token re-auth); else alias defaults toreauth-vscodeOrg.
Basic Services
Accessor pattern: call methods directly, don't assign to variable first.
- ChannelService - Output channel
- ComponentSetService - Build component sets (source, manifest, URIs)
- MediaService - Icons (ICONS) and NLS descriptions
- WorkspaceService - Workspace info
- ConnectionService - Org connections
- ProjectService - Project resolution, packageDirectories
- SettingsService - Settings read/write
- FsService - File ops (web-compatible), uri/path conversion,
HashableUri(comparisonKeyof URI fields, not.toString()) OrgMetadataCatalog- inventory/presence;getChildren/getEntries/resolveComponents. Types: catalog + entries,OrgMetadataCatalogError(type-only),OrgMetadataComponentReference,OrgMetadataCatalogChange. ADR 0021TransmogrifierService- REST/workspace SObject describe → canonicalSObject. Types:TransmogrifierService,TransmogrifierError(type-only)- EditorService - Active editor changes and current URI
- Prompts - QuickPick, InputBox, and UserCancellationError handling
- TerminalService - Run argv commands (desktop-only)
- NotificationModeService - Configurable success notifications
Watchers
File Watching
FileChangePubSub — workspace FS (**/*), including project .sf/config.json. Filter event.uri / uri.path / Utils.*, not uri.fsPath.
Global ~/.sf/config.json and ~/.sfdx/alias.json: HostFileWatcher (internal, @salesforce/core/fs). Not on the public API; services already watch them. See FileChangePubSub vs HostFileWatcher.
import * as Stream from 'effect/Stream';
const pubsub = yield* api.services.FileChangePubSub;
yield* Stream.fromPubSub(pubsub).pipe(
Stream.filter(event => /* event.uri / uri.path / Utils.*; not uri.fsPath */),
Stream.runForEach(event =>
Effect.sync(() => {
// { type: 'create'|'change'|'delete', uri }
})
)
);Config Watching
Watch VS Code config changes:
import * as PubSub from 'effect/PubSub';
import * as Stream from 'effect/Stream';
import * as Duration from 'effect/Duration';
const pubsub = yield * PubSub.sliding<vscode.ConfigurationChangeEvent>(100);
const disposable = vscode.workspace.onDidChangeConfiguration(event => {
Effect.runSync(PubSub.publish(pubsub, event));
});
yield *
Effect.addFinalizer(() =>
Effect.sync(() => {
disposable?.dispose();
})
);
yield *
Stream.fromPubSub(pubsub).pipe(
Stream.filter(event => event.affectsConfiguration('section.setting')),
Stream.debounce(Duration.millis(100)),
Stream.runForEach(() => {
// Handle config change
})
);Target Org Changes
Watch org changes via TargetOrgRef (SubscriptionRef):
const ref = yield * api.services.TargetOrgRef();
yield *
ref.changes.pipe(
Stream.map(org => org.orgId),
Stream.changes,
Stream.tap(orgId => {
// Handle org change
}),
Stream.runForEach(() => {
// Refresh UI, invalidate caches, etc.
})
);TargetOrgRef is a SubscriptionRef: ref.changes already emits the current value first, so never prepend an explicit get. See the SubscriptionRef section of ../effect-best-practices/SKILL.md for the mechanic (incl. Stream.drop(1) to skip the initial snapshot).
Ref behavior (concise):
- Default-org update: username from User SOQL when present; else
conn.getUsername()/ AuthInfo login username. Ref treated stale if stored username !== connection auth username; triggers re-query of username/userId/aliases/devHubOrgId. Write: re-getref after queries (no async gap beforeset), merge onto it,setonly if changed (any SubscriptionRef write publishes, even equivalent; TargetOrgRef subscribers like the org catalog treat each emission as an org change). Span annotatedstale: trueif ref's orgId moved (write dropped), elsechanged(+updatedif changed). - Username-less snapshot = no target org.
TargetOrgRef(DefaultOrgInfoSchema) value is always an object (neverundefined);orgId/devHubOrgIdoptional brandedOrgId(Schema.optional(OrgId), likecliId).
Clearing the Default Org
Call ClearDefaultOrgRef() to reset the in-process org ref (e.g., after deleting the default org):
yield* api.services.ClearDefaultOrgRef();Clears the reactive ref without rewriting config. Use when the CLI already mutated config but the in-process ref must reset to notify observers (e.g., the source tracking status bar icons). See orgDeleteDefaultCommand for an example.
Complete Example Pattern
// services/extensionProvider.ts
import { buildAllServicesLayer } from '@salesforce/effect-ext-utils';
export let AllServicesLayer: ReturnType<typeof buildAllServicesLayer>;
export const setAllServicesLayer = (layer: ReturnType<typeof buildAllServicesLayer>) => {
AllServicesLayer = layer;
};
// services/runtime.ts
import * as ManagedRuntime from 'effect/ManagedRuntime';
import { AllServicesLayer } from './extensionProvider';
const createRuntime = () => ManagedRuntime.make(AllServicesLayer);
let _runtime: ReturnType<typeof createRuntime> | undefined;
export const getRuntime = () => (_runtime ??= createRuntime());
// index.ts
import { buildAllServicesLayer } from '@salesforce/effect-ext-utils';
import { nls } from './messages';
import { myCommandEffect } from './commands/myCommand';
import { setAllServicesLayer } from './services/extensionProvider';
import { getRuntime } from './services/runtime';
export const activate = async (context: vscode.ExtensionContext) => {
setAllServicesLayer(buildAllServicesLayer(context, nls.localize('channel_name')));
await getRuntime().runPromise(activateEffect(context));
};
export const activateEffect = Effect.fn(`activation:${EXTENSION_NAME}`)(function* (_context: vscode.ExtensionContext) {
const providerService = yield* ExtensionProviderService;
const api = yield* providerService.getServicesApi;
yield* api.services.ChannelService.appendToChannel('Extension activating');
const registerCommand = api.services.registerCommandWithRuntime(getRuntime());
yield* registerCommand('sf.my.command', myCommandEffect);
yield* api.services.ChannelService.appendToChannel('Extension activation complete.');
});Testing
Mock services via Layer.succeed and combine with Layer.mergeAll. For static accessors (e.g., api.services.WorkspaceService.getWorkspaceInfo()), wire both the provider and service:
import { ExtensionProviderService } from '@salesforce/effect-ext-utils';
import { WorkspaceService } from 'salesforcedx-vscode-services/src/vscode/workspaceService';
import * as Effect from 'effect/Effect';
import * as Layer from 'effect/Layer';
// Mock both ExtensionProviderService and WorkspaceService
const mockWorkspaceLayer = Layer.mergeAll(
Layer.succeed(ExtensionProviderService, {
getServicesApi: Effect.succeed({
services: { WorkspaceService } // Accessor sees real class
} as unknown as SalesforceVSCodeServicesApi)
}),
Layer.succeed(
WorkspaceService,
new WorkspaceService({
getWorkspaceInfo: () => Effect.succeed({ path: '/mock', fsPath: '/mock', isEmpty: false, isVirtualFs: false, cwd: '/mock' }),
getWorkspaceInfoOrThrow: () => Effect.succeed(/* ... */)
} as unknown as WorkspaceService)
)
);
// Use in test
const result = await Effect.runPromise(
myEffect().pipe(Effect.provide(mockWorkspaceLayer))
);For direct service mocking (no accessor), use Layer.succeed(Service, mockImpl) alone.
Common Patterns
- Start with
api.services.prebuiltServicesLayer— don't add individual*.Defaultfor services already there - Only add per-extension layers on top
import { ICONS }outside Effect;MediaServiceinside EffectChannelServiceLayerbeforeErrorHandlerService- Pass
contexttoSdkLayerFor(extracts name/version from ExtensionContext) Effect.forkIn(..., yield* getExtensionScope())for watcher cleanup on deactivation- Scoped services own their VS Code disposables via finalizers; runtime disposal runs them
registerCommandWithRuntimefor all commands (tracing + error handling)- Use
getRuntime().runPromise/runForkinstead ofEffect.provide(AllServicesLayer)for execution
Don't: rebuild services already in prebuiltServicesLayer
// WRONG — creates new singleton instances, duplicating caches/watchers/state
return Layer.mergeAll(
ExtensionProviderServiceLive,
api.services.ExtensionContextServiceLayer(context),
api.services.FsService.Default, // ← already in prebuilt
api.services.AliasService.Default, // ← already in prebuilt
api.services.SdkLayerFor(context),
channelLayer,
errorHandlerWithChannel
);
// CORRECT — share the already-built singletons
return Layer.mergeAll(
api.services.prebuiltServicesLayer,
ExtensionProviderServiceLive,
api.services.ExtensionContextServiceLayer(context),
api.services.SdkLayerFor(context),
channelLayer,
errorHandlerWithChannel
);Review
Invoke the effect-advocate subagent on plans and diffs — its top-priority finding category is "you re-implemented something that already exists in salesforcedx-vscode-services."
prebuiltServicesLayer contains ~27 services built once during services extension activation. Calling .Default on any of them creates a second instance with its own caches, watchers, and state — silently breaking cross-extension sharing.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

