- Install @a2a-js/sdk v0.3.13 as a dependency
- Add a2a-transport.js: A2ATransport class with spawnAgent, dispatch,
getOrSpawnAgent, and buildAgentCard; spawns pi subprocesses with
SF_A2A_AGENT_* env vars and dispatches envelopes via A2A JSON-RPC
- Add a2a-agent-server.js: A2A HTTP server entrypoint for spawned agent
processes; starts express + A2AExpressApp with DefaultRequestHandler,
handles incoming DispatchEnvelopes via SwarmAgentExecutor, writes
envelope to SQLite MessageBus, and signals readiness via stdout JSON
- Update swarm-dispatch.js: split dispatch() into _busDispatch()
(existing SQLite path) and _a2aDispatch() (new A2A path); lazy-load
A2ATransport singleton only when SF_A2A_ENABLED is set; default
path unchanged for all existing callers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create config.ts with McpServerConfig types and readMcpConfigs/getServerConfig
- Create auth.ts with buildHttpTransportOpts and createCliOAuthProvider
- Create connection-manager.ts with McpConnectionManager class
- Create index.ts re-exporting the public API
- Export McpConnectionManager and helpers from @singularity-forge/coding-agent
- Rewrite mcp-client extension as thin wrapper using McpConnectionManager
- Rewrite auth.js as re-export shim from @singularity-forge/coding-agent
- Update test to import buildHttpTransportOpts from @singularity-forge/coding-agent
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sf-tui was a 'bundled' extension with zero features independent of the sf/
extension. Every hook, shortcut, tool, header and footer render depended
on sf/ internals (getAutoSession, isAutoActive, projectRoot,
getExperimentalFlag). The separation was artificial.
Changes:
- Moved all sf-tui/*.js into sf/ui/ (header, footer, git, color-band, emoji,
prompt-history, marketplace, powerline, shared)
- Fixed imports: ../sf/ → ../ (one level up from ui/)
- Registered sf/ui/index.js from sf/index.js in a try/catch so a UI failure
can't take out the core SF commands
- Merged sf-tui manifest entries (9 commands, 3 shortcuts, agent_start hook)
into sf/extension-manifest.json
- Deleted src/resources/extensions/sf-tui/ entirely
- Fixed prompt-history.test.mjs import path
Result: one fewer extension to discover, load and validate at startup.
sf is now the single extension that owns both planning state and UI chrome.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Ink bridge added today was a misguided gradual-migration wrapper:
- Components still rendered via the old string-line protocol (no Ink layout)
- Key decodes were re-encoded to escape sequences → keys.ts decoded again (double round-trip bug)
- The _useInk / _inkHandle path blocked TTY start unconditionally via process.stdout.isTTY check
Removed: ink-bridge.tsx, ink-bridge.test.ts, useInk() method, _useInk/_inkHandle fields,
startInkRenderer import/export, Ink branch in start()/stop()/requestRender().
Removed ink and react from packages/tui dependencies and peerDependencies.
Reverted tsconfig.extensions.json jsx settings (only needed for the .tsx bridge file).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tsgo (TS7 native port) requires explicit jsx setting when .tsx files are
in scope. tsc 6 was lenient; tsgo errors without it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New installations create .sf/preferences.yaml (pure YAML, no frontmatter
markers) and ~/.sf/preferences.yaml. Existing .md files are read as fallbacks
with no migration required for current users.
Changes:
- preferences.js: add yaml path getters, load chain tries .yaml first, add
parsePreferencesYaml() for direct YAML parse without frontmatter extraction
- templates/preferences.yaml: new canonical template (pure YAML with comment
header pointing to preferences-reference.md)
- gitignore.js: ensurePreferences() creates preferences.yaml; simplified by
removing scaffold-versioning dependency
- init-wizard.js: buildPreferencesFile() produces pure YAML, writes preferences.yaml
- commands-prefs-wizard.js: savePreferencesFile() helper handles .yaml vs .md;
ensurePreferencesFile uses yaml template for yaml paths
- preferences-template-upgrade.js: yaml files get raw YAML on upgrade
- planning-depth.js: returns {path, isYaml}, handles both formats
- deep-project-setup-policy.js: isWorkflowPrefsCaptured() tries all 3 paths
- detection.js: preferences.yaml added to all detection checks
- auto-worktree.js: canonical=yaml, LEGACY_PREFERENCES_FILES=["PREFERENCES.md","preferences.md"]
- auto-bootstrap-context.js: preferences.yaml before PREFERENCES.md in list
- guided-flow.js / worktree-root.js: existence checks include preferences.yaml
- User-visible strings / comments updated throughout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Prompts: replace 'append to .sf/DECISIONS.md' → 'call save_decision' in
plan-slice, heal-skill (KNOWLEDGE.md), refine-slice, queue, guided-execute-task
- Prompts: replace 'Read .sf/DECISIONS.md if it exists' / 'Read .sf/REQUIREMENTS.md if it exists'
with 'injected from DB into system context' in guided-plan-slice, guided-research-slice
- requirement-promoter: remove dead appendRequirementRow() and readHighestRNumber(file)
that read/wrote REQUIREMENTS.md; replace with DB-only readHighestRNumber() using
getActiveRequirements(); remove sfRoot import, mkdirSync, writeFileSync
- requirement-promoter: pre-compute highestNum once per sweep loop instead of
re-reading for each cluster (fixes ID collision when promoting multiple at once)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- auto.js, auto/loop.js, bootstrap/register-hooks.js: tag all
autonomous-mode system notices with NOTICE_KIND.SYSTEM_NOTICE;
add dedupe_key to loop-level model-policy and flow-audit notices
- web/notifications-service.ts: add repeatCount/lastTs/noticeKind to
Notification type (schema v2 fields)
- uok/trace-writer.js: new unit trace writer
- tests/notification-store-grouping.test.mjs: grouping test coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The function and node:fs/os/path imports were dropped from the source
during editing. Added them back. Updated memory-embeddings-llm-gateway
test to cover auth.json-only behavior (no env var aliases).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gateway key and URL are now read exclusively from ~/.sf/agent/auth.json
under the 'llm-gateway' entry. Removed env var support for the API key
(SF_LLM_GATEWAY_KEY, LLM_MUX_API_KEY, etc.) — credentials belong in
auth.json alongside all other provider keys, not in the environment.
Model/instruction overrides (SF_LLM_GATEWAY_EMBED_MODEL etc.) still
read from env vars as they are tuning knobs, not secrets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete memory-backfill.js — not imported anywhere, dead code
- Rename memory-sleeper.js → tool-watchdog.js — misnamed; it is a
tool-output watchdog with no relation to the memory store
- Collapse memory-embeddings-llm-gateway.js into memory-embeddings.js —
removes the lazy-import split; loadGatewayConfigFromEnv,
createGatewayEmbedFn, and rerankCandidates are now direct exports
- Remove buildEmbeddingFn() dead stub (always returned null)
- Enable packages/coding-agent memory extraction extension by default
(memory.enabled ?? true) so session-level extraction is active
- Update all import sites and tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Local SQLite is the memory system. External Singularity Memory is an
optional cross-project enhancement, not a dependency. Flip the default
so SM is disabled unless explicitly opted in via SM_ENABLED=true:
- sm-client.js: return disconnected early unless SM_ENABLED=true
- memory-store.js: only pass smConnected=true when SM_ENABLED=true
- doctor-config-checks.js: skip SM health check when not opted in
- sm-client.test.ts: update test to reflect opt-in behaviour
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- knowledge-compounding.js: replace KNOWLEDGE.md file-read dedup with
getActiveMemories() DB query; file was never written so dedup was
always empty, causing duplicates to accumulate on every milestone close
- knowledge-compounding.js + save_knowledge tool: map confidence strings
('high'/'medium'/'low') to numeric scores (0.9/0.6/0.3) for the
memories.confidence REAL column; string values coerced to 0.0 by
SQLite, silently making all knowledge entries rank last and never
appear in system context
- save_knowledge: use K-${randomUUID()} (full UUID) instead of
K-${randomUUID().slice(0,8)} to match knowledge-compounding.js and
avoid collision risk
- complete-milestone.md: replace '.sf/DECISIONS.md' file reference with
'decisions inlined from DB' — the file is not generated anymore
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verifies the function handles null/undefined content gracefully and
correctly extracts goal, demo, verification, and observability sections
from slice plan content. Addresses sf-mozutl5d-ei3ec6 by ensuring the
function is importable and behaves correctly end-to-end.
- doRender() now catches render errors and emits a fallback line
- autonomousStatus ANSI formatting extracted to renderAutonomousStatus()
with named color constants instead of raw escape strings
- parseCellSizeResponse extracted to pure function with proper validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- TUI.useInk() opts into Ink-backed rendering (call before start())
- In start(): if _useInk || process.stdout.isTTY, mount Ink renderer via
startInkRenderer() and skip the legacy differential render path entirely
- In stop(): unmount Ink handle and return early; legacy terminal cleanup
(cursor repositioning, showCursor, terminal.stop) is skipped since Ink
handles terminal restoration itself
- Passes this.render()/invalidate() via a plain Component wrapper to avoid
the private handleInput TypeScript conflict
- Two new contract tests: useInk() flag and stop() Ink handle teardown
- 80/80 tests pass; legacy path unchanged for non-TTY (CI/tests)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Install ink@7.0.2 + react@19.2.6. Add JSX/react-jsx support to
packages/tui tsconfig. Create ink-bridge.tsx: LegacyComponentView wraps
existing Component objects as React nodes, startInkRenderer drives the
Ink render loop around any legacy Component tree.
Exports startInkRenderer from @singularity-forge/tui public API.
All 78 existing tui tests pass; 3 new ink-bridge tests added.
This is the infrastructure step for migrating components one-by-one from
the custom differential renderer to native Ink React components, without
breaking interactive mode.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove vestigial experimentalDecorators/emitDecoratorMetadata from all
package tsconfigs (no actual decorators in source — flags were from
pi-mono vendor copy)
- Add @typescript/native-preview for 8-10x faster type checking (measured
4.6x on this repo: tsc 6.5s vs tsgo 1.4s)
- Fix tsconfig.extensions.json: remove baseUrl (removed in tsgo/TS7) and
use relative paths in paths mappings — compatible with both tsc and tsgo
- Add typecheck/typecheck:extensions scripts using tsgo
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- sf-db.js: add backfillUatVerdicts(basePath) that scans ASSESSMENT/UAT_RESULT
files for slices with no uat_verdict in DB and populates them on open
- dynamic-tools.js: call backfillUatVerdicts after openDatabase succeeds so
all 3 repos with existing verdict files are covered on next launch
- workflow-tool-executors.js: call setSliceUatVerdict when saving ASSESSMENT
at slice scope so future verdicts are written directly to DB
- workflow-helpers.js: remove all file fallbacks from checkNeedsRunUat;
verdict check is DB-only (backfill guarantees DB is populated on open)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
checkNeedsRunUat only checked for UAT_RESULT file, but the autonomous
runner writes ASSESSMENT files. This caused run-uat to dispatch 5x with
no verdict when only an ASSESSMENT (with verdict: PASS) existed.
Now ASSESSMENT file with any verdict counts as a completed UAT result,
stopping the infinite dispatch loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously required /autonomous first. Now any slash command (/next, /chat,
/clear etc.) caches the ExtensionCommandContext, so Ctrl+Y YOLO shortcut
works on first press after any command interaction.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shortcut handlers (registerShortcut) receive ExtensionContext which has
no newSession(). This caused autonomous mode started via Ctrl+Y to
always crash with 'newSession is not a function'.
- AutoSession.lastCommandCtx: new field that persists across stopAuto/reset
so shortcut handlers can fall back to the last valid command context
- startAuto(): cache valid command ctx; fall back and notify user if ctx
has no newSession; return early with actionable message if no cache yet
- dispatchHookUnit(): same guard — resolve hookCtx before s.cmdCtx = ctx
- run-unit.js: last-resort guard before newSession() call returns clean
error category instead of TypeError
- steerable-autonomous-extension.js: rename ctrl+y → ctrl+alt+y to avoid
conflict with terminal yank built-in
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite all 13 renamed tool descriptions to follow Copilot tool conventions:
- Imperative verb opening
- One sentence on what it returns
- One sentence on when to use it
- No internal jargon or SF-specific acronyms
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- fix(compaction): tokensBefore undefined crash on reload
compaction-orchestrator now falls back to preparation.totalTokens when
extension returns tokensBefore: undefined; compaction-summary-message
guards with ?? 0 defensively
- feat(exec): inline truncation notice in sf_exec digest
appends [stdout truncated — read full output: <path>] when
stdout_truncated=true so agent knows to use sf_exec_search
- feat(exec): wire onUpdate progress for sf_exec
calls onUpdate before execution starts with status/command so TUI
shows live feedback during long-running commands
- feat(security): prompt injection defense for external content
new sanitize-external-content.js utility: strips HTML comments,
detects 15 injection patterns (instruction override, role reassignment,
fake system messages, encoded payloads); wired into exec-tool digest
- feat(tools): sf_session_todo tool (persisted cross-compaction)
add/check/list ops; persists to .sf/session_todo.json; pending todos
injected into compaction summary block for context continuity
- feat(hooks): shell hooks surface (.sf/hooks/pre-tool/*.sh, post-tool/*.sh)
pre-tool hooks block tool execution (exit≠0 = block with stdout reason)
post-tool hooks fire-and-forget; JSON context piped to stdin; 5s timeout
- fix(db): WAL autocheckpoint disabled to prevent corruption
PRAGMA wal_autocheckpoint=0 in initSchema(); explicit checkpointWal()
after successful finalize verification — the only safe checkpoint point
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- unit-runtime: fall back to STATE.md for nextActionAdvanced when DB is
unavailable (restores test compat for reconcileDurableCompleteUnitRuntime-
Records; DB path still preferred in production)
- browser-slash-command-dispatch: remove 'stop' from SF_PASSTHROUGH_COMMANDS
so /stop correctly returns { kind: 'reject' } in browser mode (was falling
through to prompt/rpc instead of builtin-reject)
- bg-events: export MAX_PENDING_ALERTS so process-manager can re-export it;
satisfies session-memory-leaks contract test
- commands-handlers: guard effectiveScope assignment — only use requestedScope
when mode=audit AND requestedScope is truthy (avoids undefined propagation)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove setFooter(hideFooter) calls in auto-start.js and auto.js that were
overriding the sf-tui footer with a near-invisible stub. The sf-tui footer
already checks isAutoActive() and routes to renderAutoFooter — no override
needed. Also remove now-unused hideFooter import from auto.js.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>