Merge with upstream#2
Open
Outcue wants to merge 773 commits into
Open
Conversation
…ith the same expression.
… code to co-exist in a document. To this end, amplets are assigned ids by the system rather than using their definition as a key. In addition, the worksapce manager now allows user-named workspaces to be generated via its API, which makes it easier to open workspaces in a document, keeping these distinct yet also maintaining their state.
…ng of live widgets embedded in HTML via the mapping rather than via amplets. This restores that.
… are still problems howver.
…e up to date when revisited.
…at if it is open, it stays open (and indeed is recursively updated if its internals depend on the model, and retains any state it has recursively as well).
Update version number
…d by default and eliminating explicit Ordered variants. Depends on changes to psoup repo.
…apping to cache amplets so updates would work properly. The updates work, but editing the amplet in the HTML doesn't work well. This fixes that, but is expensive and breaks some code (like Ozymandias). Further work should address that.
… effect unles sthe ampletID is changed. Now the amplets update, but no computation is done if the expression has not changed. Also, it doesn't seem to break anything (the other attempt broke Ozymandias for unknown reasons).
… for embedded windows. Needs testing and improvements, as well as UI support for splitting and unifying windows.
The AI chat could read the IDE but not drive its view. Add five side-effecting tools so the model can open what it has just looked up: - navigate_to_class, navigate_to_namespace, navigate_to_document - show_tests / run_tests for test configuration classes Browsing exposes a new public navigateTo: aSubject so external callers can drive currentWindow enterSubject: without holding a presenter. AI_IDE_Support adds the tool definitions, a namespaceSubjectForPath: helper, and imports ActivationMirror as a slot (factory parameters are not in scope inside methods). run_tests mirrors respondToRunTests using ActivationMirror invokeSuspended: so a raise routes to a debugging thread. The system prompt gains a side-effecting-tools subsection so the model knows to use them only when the user has expressed intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… on CLI and in IDE.
Lets the AI modify code in the running IDE via two tools: propose_changes
stages a changeset and returns a textual diff plus an id; apply_changeset
runs the platform's atomic installer on the staged set. The user reviews
the diff in chat and only approved changesets ever touch the system.
Supported change kinds:
add_or_replace_method, delete_method
add_or_replace_lazy_slot, delete_lazy_slot
set_class_header
add_or_replace_nested_class, delete_nested_class
add_top_level_class
Multiple changes in one propose_changes call install atomically — meant
for mutually-dependent edits. Top-level deletion and class rename are
deliberately out of scope (different mechanism / non-atomic).
Mechanics worth noting:
* Changes are aggregated per top-level class into one
ClassDeclarationBuilder; nested navigation re-uses the cached top
builder. When descending via instanceSide.nestedClasses we patch the
nested IR's enclosingClass to the parent's instance-side
IntermediateMixin so the compiler's scope walk can reach the top
(the mirror system's lazy patch-up only runs on the bottom-up path
via mirror.asBuilder).
* primordialsoup's AtomicInstallWrapper returns mirrors but does NOT
put new top-level classes into Root; we mirror Browsing's own
post-install step (applyToObject reflectee → Root at: name).
Focus refactor — brain buttons no longer track focus by Subject identity,
which broke after any method replacement: atomic install forwards mixin
objects but not individual methods, MethodMirror equality fails,
MirrorGroupSubject>>updateElements creates a fresh MethodSubject, and the
old owner-equality check goes dark. Focus now consists of three
orthogonal pieces of state in AI_IDE_Support:
* a description block, returned by the current_focus tool
* a validator block, run on every current_focus call so a deleted or
replaced-away entity drops focus automatically (works uniformly for
explicit deletes and implicit ones like add_or_replace_nested_class)
* structured identity slots (kind / classPath / side / selector), used
by isFocusedOnX:... matchers for brain-button highlighting
Validators compare via ClassDeclarationMirror>>= (which checks the
underlying mixin), so a replaced class — whose mixin has been forwarded
— still matches; only deletions drop out.
The dead owner-based API (setFocus:, setFocus:owner:, isFocusedOwner:,
currentFocusOwner_slot) is removed; the kind-specific setXFocusOn:
helpers are the only public way to install focus. aiChatButton: now takes
an explicit ifFocused: predicate from each brain site.
Also cleaned up `slot ::= nil` to bare `slot` in AIAccess, AIChatUI, and
AI_IDE_Support — standalone mutable slots are nil-defaulted; the form
without an initializer takes no period.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… responses
Model picker
AnthropicProvider's model slot is now mutable so the chat can switch
models mid-session. The session's apiMessages history is provider-
agnostic (just text / tool_use / tool_result blocks), so switching just
routes the next turn to a different model with the full transcript as
context. AnthropicProvider exposes a curated availableModels list and
displayNameFor: with friendly labels for the picker.
ChatPresenter's heading bar grows a model picker — a label of the
current model plus a dropdown. The picker is disabled (rendered as a
dim label) while a request is in flight, to avoid switching in the
middle of a tool-use loop.
AIChatSetupSubject's default modelName is now claude-sonnet-4-6,
replacing the year-old claude-sonnet-4-20250514. The legacy id is
retained in availableModels for back-compat with existing chats.
Chat help text expanded to describe the picker, available models,
history preservation, the first-turn cache miss after a switch, and
the in-flight guard.
Defensive response handling
completeWithToolLoop now validates that the API response carries a
content array before mutating apiMessages. We have observed
intermittent 200-OK responses missing content (CDN edge case /
refusal-with-no-body / transient server glitch) — previously the
undefined value got stored as an assistant message, which corrupted
history and broke every subsequent send. Now it raises a clear error
("API response missing content. stop_reason: ...") without touching
state, so a retry just works.
displayMessages picks up a matching guard so any pre-existing
corrupted-content message in a long-running chat doesn't crash the UI.
Non-OK API responses now include statusText alongside the numeric
status — so "Anthropic API error: 429" becomes
"Anthropic API error: 429 Too Many Requests".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…error handling
Evaluation surface (AI_IDE_Support)
Five new tools — evaluate / navigate_to_object / inspect_object /
list_objects / forget_object — let the AI run expressions and refer to
results across turns:
- The AI workspace is the default receiver; created lazily as a real
IDE workspace (class AIWorkspace, subclass of Workspace, visible to
the user via the standard workspace list) by going through
WorkspaceManager.AllWorkspacesSubject>>getWorkspace:.
- Non-primitive results are stored in a per-session object registry
under auto-generated ids (r1, r2, ...) or AI-supplied names. The AI
refers to them via in_id on later evaluate calls.
- 'workspace' and 'focus' are pre-registered ids. 'focus' resolves
dynamically to the currently focused evaluator's receiver (see
below) — letting the AI write evaluate("...") and have it default
sensibly to either the workspace or whatever evaluator the user has
indicated.
Chats on evaluator panes (Browsing)
EvaluatorPresenter — the base class shared by workspaces, object
inspectors, in-method evaluator panes, lazy-slot evaluators, and
debugger activations — grows a brain button in its button row plus a
chatSection in its definition. Clicking installs a new #evaluator
focus kind whose identity is the evaluator's underlying mirror
(ObjectMirror or ActivationMirror). When that focus is active, the
AI's evaluate default routes through it via the 'focus' id, results
are appended to the focused evaluator's results list, and
Browsing>>updateCurrentDisplay (new) re-renders the IDE so the user
sees them live.
The validator handles activation lifetime: focus on a debugger frame
invalidates automatically once ActivationMirror>>isUncontinuable
becomes true (frame resumed or otherwise disposed), clearing the
focus on the next current_focus check.
Workspace UI refresh (AI_IDE_Support, Browsing)
Workspace evaluations now also append the ThreadMirror to
aiWorkspaceSubject's evaluator results list and call
updateCurrentDisplay, so AI-driven evaluations show in the AIWorkspace
presenter both live and on later navigation. The same pattern applies
to focused evaluators.
Defensive tool error handling (AIAccess)
executeToolCall:'s exception path now wraps e messageText in a fall-
back chain (messageText → printString → "<unprintable exception>")
so an oddly-shaped exception (e.g. a CannotReturn raised from a stale
stored block) can no longer silently hang the chat.
Memory note added for future me on the underlying bug: ^ inside a
stored block raises CannotReturn after the defining method exits;
tool handlers must use ifTrue:ifFalse: rather than early returns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ixes
Test result and re-run tools (AI_IDE_Support)
run_tests captures the testing subject keyed by config simple name.
Five new tools work against the cached Tester:
get_test_results counts plus per-failure/error one-liners
get_test_failure_details full description / exception detail
run_failing_tests re-run current failures
run_erroring_tests re-run current errors
run_test re-run a single named test
Test cases are identified by "ContextName >> test_selector".
Selective re-runs iterate a copy of the failure/error list (tester
run: mutates the lists) and updateCurrentDisplay so the IDE view
refreshes live. (rounded — not truncated — on the completion ratio;
Float in primordialsoup doesn't define truncated.)
Chat on test outcome view (MinitestUI)
TestingOutcomePresenter — already a ProgrammingPresenter — gains a
brain button in its heading row and a chatSection in its column.
A new #testRun focus kind identifies the run by config class path,
with description "Test results: <ConfigName>". Validator uses
classStillExists:.
Run Again creates a brand-new TestingOutcomeSubject (different object),
so Hopscotch's updateVisualsFromSameKind: chat-restore path doesn't
apply — the new presenter's chatHolder starts empty. Definition now
schedules an installAIChat when a matching #testRun focus is active,
so the chat region persists across re-runs without the user having
to re-click the brain. TestingInProgressPresenter is left alone (it
extends Presenter not ProgrammingPresenter, is transient, and is
navigated away from as soon as tests complete).
Chat input accept-button fix (AIChatUI)
Clicking the green accept button used to silently fail — CodeMirror's
setValue fires a 'change' event, which our changeResponse handler
used to write the editor text back into subject draft, so by the
time sendDraft ran, the draft was empty and the message wasn't sent.
Cmd-Return won that race. The fix bypasses subject draft entirely:
capture text locally, push it through a new ChatSubject>>sendText:
that doesn't read draft. Clear editor + leaveEditState happen BEFORE
updateGUI so the old fragment's edit-state machinery handles them
rather than a fresh post-update fragment whose handlers don't see
the old state (which had been causing the accept/cancel bar to stop
appearing on subsequent edits).
Auto-close other chats on the same page (Browsing, AI_IDE_Support)
Without coordination, clicking a different brain leaves the previous
chat region visible — multiple chat panels at once. AI_IDE_Support
tracks the active (chatHolder, pageSubject) pair; on brain click,
the previous holder is cleared ONLY when the new click is on the
same page. Cross-page navigation preserves each page's chat: A's
chat survives A → click B → click → back to A. Page identity is
Browsing's currentWindow currentPresenter subject, surfaced via
Browsing>>currentPageSubject.
Boolean and: chains one block, not two — Smalltalk-ism caught after
the first build broke per-presenter brains with "False and:and:".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…list/delete media Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…us eof conflict with accept/cancel buttons.
…s the problem that the top line may not be visible if the line is short or overlaps with the accept/cancel buttons. Change show evey editor displays changes, so not great. Eventually, w emay want to change how the display of changes works so we get a signal distinct from the change buttons.
…egration, multi-doc switching, live-view reactivity, generic-doc brain, defensive extractTextFrom Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…key storage, defensive translation, surface API error bodies Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se/Newspeak-exception bridge bug, surface API errors as in-chat turns, add provider chooser to setup Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hopscotch now owns AIChatUI as a lazy module instance constructed from a platform-level aiAccess and an aiChatUIClass re-export, so HopscotchWebIDE and AI_IDE_Support no longer instantiate the AI modules themselves. They pull presenter classes and the chat module through platform hopscotch, keeping the Hopscotch factory signature unchanged. Also a type-annotation and comment pass across AIAccess, AIChatUI, and AI_IDE_Support to document the provider abstraction, the chat-document projection model, and the JS Promise/then: rejection idiom that replaced in-closure Error signal: raises. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The chat UI classes (AIColors, AIChatSetupSubject, AIChatSetupPresenter, ChatSubject, ChatStatusPresenter, ChatInputPresenter, ChatPresenter) now live as sibling top-level classes inside HopscotchForHTML5 rather than as a separate AIChatUI module. This removes the construction cycle that required Hopscotch to retain a private platform reference and a lazy aiChatUI slot, and removes the unnatural Platform.aiChatUIClass accessor. AIAccess slot block is reshaped to do only class imports plus a platform handle; its JS-side handles (global, JSON, Promise) become lazy module-body slots in AIAccess itself. This lets Hopscotch's slot block safely call `p aiAccess` during platform init, since AIAccess construction no longer touches JS aliens that aren't yet usable at snapshot time. AI_IDE_Support drops its chatUI slot and pulls colours through a new aiColors slot — a single small utility class on Hopscotch keeps the import surface tidy. The Hopscotch factory signature is unchanged, so Croquet and other Hopscotch consumers don't need any edits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GeminiProvider previously inherited the Provider default hasToolUse:
^false, so the Session's tool loop was bypassed entirely. It now
translates Gemini function-calling to and from the same tool-block
surface Session already drives for Anthropic.
Provider gains addToolResults:toMessages: as a new required hook for
tool-supporting providers, factored out of completeWithToolLoop's
inline tool_result-message assembly. AnthropicProvider's implementation
is the lifted inline code unchanged. GeminiProvider's wraps the round's
parts as {role: 'user', parts: [...]}.
Gemini-side translation:
- buildToolsPayload: produces [{functionDeclarations: [...]}], with each
declaration using 'parameters' (Gemini) rather than 'input_schema'
(Anthropic) for the JSON schema.
- hasToolUse: / toolUseBlocksFrom: scan candidates[0].content.parts for
functionCall parts. Gate on functionCall.name being non-nil, not on
functionCall itself — on text-only parts the missing functionCall
field surfaces as JS undefined which fails isNil, and a fc-isNil-
ifFalse guard incorrectly admits text parts and yields malformed
shims.
- Gemini issues no tool-call ids of its own, so toolUseBlocksFrom:
synthesises a 'gem-N' id per call and stashes the name in a per-round
pendingCallNames map. The shim blocks expose the same 'id', 'name',
'input' surface Anthropic emits, so Session.executeToolCall: needs no
provider-specific path.
- makeToolResult: builds a functionResponse part, recovering the
function name from pendingCallNames. Gemini has no standard is_error
flag; failure text goes under 'error' instead of 'result'.
- doRequest: now accepts and plumbs the tools payload into the request
body; complete: and attemptComplete: thread it through.
Bulletproof alien field reads (cross-cutting; surfaced during Gemini
bring-up but applies to both providers):
- New Provider helper safeStringAt: receiver field:. Returns the value
only if a real String (verified by v , '' succeeding). Returns nil
for missing fields, JS undefined, JS null, numbers, objects — any-
thing that fails the no-op concat. Cheap, no JSON round-trip.
Necessary because primordialsoup's alien bridge does not uniformly
convert JS undefined to Newspeak nil; a missing field on a JS object
often returns JS undefined which doesn't satisfy isNil, and the
previous isNil-ifFalse guards admitted it into String concats and
crashed with ArgumentError.
- All scalar string-leaf reads in both providers' response-walking code
(extractTextFromResponse:, displayMessagesFromMessages:, hasToolUse:,
toolUseBlocksFrom:) now go through safeStringAt:. safeAt: continues
to handle structural navigation (returns nil for missing receivers /
fields but doesn't normalise undefined leaves).
Robust error surfacing:
- New module-body errorDescription: helper extracts a non-nil String
from any exception. Tries messageText, falls back to class name
printString, then printString, then '<unprintable exception>'. Each
step wrapped so no failure in the chain can sink the whole error
path.
- The four Promise.reject sites (AnthropicProvider doRequest:,
GeminiProvider doRequest:, Session completeWithToolLoop, webFetch:)
now route through errorDescription: instead of bare e messageText,
which was nil-prone and made the chat error turn read 'nil'.
- Session completeWithToolLoop's outer catch now ships the raw provider
response in the rejection ('[stage: X] <error> | response: {...}'),
so future protocol drift or a new provider's first unexpected
message shape lands directly in the chat — no fresh diagnostic
instrumentation needed when the AI vendor tweaks their format or a
new provider follows the recipe.
- Stage tracking in completeWithToolLoop and executeToolCall: pinpoints
which substep of response processing failed. JSON dump of the
problematic tool-call block in executeToolCall:'s structural-error
paths surfaces malformed shims directly.
Browsing.ns: ProgrammingPresenter>>aiChatButton:ifFocused: gained
multi-stage error reporting on click. The previous on:Error do:
formatted 'AI chat error: ', e messageText, which itself crashed when
messageText was nil — silently turning a recoverable UI failure into a
hard IDE freeze. The new handler tracks the substep being executed
(read activeChatHolder, install focus, install AI chat, etc.) and uses
a nested catch that falls back through messageText, class name, and
printString.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hat doc Three related cleanups to the AI-chat entry points. Hide-key toggle (HopscotchForHTML5.ns AIChatSetupPresenter): - The API key editor now lives inside a ToggleComposer. Initial state follows a new keyRevealed slot: revealed (=true) on first-time setup when subject apiKey is empty, concealed (=false) once a key has been stored. The user can expand or collapse manually; the editor's acceptResponse: collapses the toggle on Accept and respondToStart collapses on successful chat start. Both push keyRevealed:: false via updateGUI:, but the close also calls keyToggle collapse directly to mutate the *old* toggle's isExpanded — ToggleComposer's update- VisualsFromSameKind: copies isExpanded from the old fragment, so a fresh toggle with initiallyExpanded:false alone wouldn't actually close after the first render. Provider-switch resync (AI_IDE_Support.ns IDEChatSetupPresenter): - Replaces the inline providerPicker action blocks with a new respondToProviderSwitch:defaultModel: that resyncs presenter state to the newly-chosen provider. Just mutating subject providerName / modelName / reloadApiKey wasn't enough: ToggleComposer preserves old isExpanded across re-renders, and CodeMirrorFragment preserves the old editor's text, so the visible form lagged behind the subject. The new method explicitly toggles keyToggle, updates keyEditor's textSlot, and updates modelEditor's textSlot in the same updateGUI: block, so the diff inherits the right state. Toolbar → chat doc (AI_IDE_Support.ns chatLauncher): - Toolbar AI Chat button now navigates to the active chat doc's full DocumentSubject view (Ampleforth contents with chatControls, doc switcher, model picker, structural amplets, turns as HTML sections) — the canonical home of the chat. Previously it landed on the bare IDEChatPresenter view, which lacks all of those. ensureSharedChat (used by per-presenter brain buttons for the inline ephemeral chat region) is unchanged; the two paths now give appropriately different UX for the same conversation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ChatDocument>>noteLiveViewRendered: stores the current TwoViewEditor on the doc each time its DocumentPresenter renders, but the slot is never cleared when the user navigates away. A chat-subject side effect (sendText:, markComplete, markError:) firing AFTER navigation — e.g. a tool-loop response resolving on a chat we just switched away from — then calls refresh on the now-detached editor. That walks into shell on a parent-less DocumentPresenter and raises 'hierarchy not installed in a shell'. Fix: refreshLiveView probes the cached editor's liveness by asking for shell; if walking the parent chain raises, the slot is stale, so we drop it and return. The next render of this doc installs a fresh editor via noteLiveViewRendered: and subsequent refreshes work against that. This is a narrow patch for the immediate footgun. The broader chat- document reactivity / deactivation story still wants a proper hook (see CHAT_SWITCHING_FOLLOWUPS_2026-05-18.md item 1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings Matthias Kleine et al's MemoryHole (Pur version-control
abstraction; see HPI tech report 2012) into the primordialsoup-side
codebase. Phase 1 of the port: get the abstraction core to load
against the web platform. No backend, no UI, no actual VCS
operations yet — those come in follow-up commits.
The slot-block edits split roughly into:
- Squeak collections / streams imports swapped to web equivalents
(Set / List / Map → p collections; OrderedCollection / Dictionary
go away).
- Squeak-only deps that have no web counterpart yet (Stream,
ExternalLauncher, Monitor, MultiByteBinaryOrTextStream,
CrLfFileStream, Transcript, FileDirectory; TextDiffBuilder,
DiffElement, Text/TextColor/TextEmphasis/RunArray; PackageInfo,
PNGReadWriter, Script) → bound to nil. Their consumers are all in
backend-or-rendering code paths the abstraction core doesn't
exercise; phase 2 will provide them via concrete backends or
reimplementation.
- VCSDiffing: Character constants (Character tab / space / cr / lf /
newPage) aren't reachable in this module's scope on the web
platform without an explicit import path; stubbed with literal
whitespace and Set new. Same fate for String>>asSet which isn't
in the web collections.
- VCSNewspeakSourceMirrors: `p mirrors grammar TypedNS3Grammar` MNUs
on MirrorsForPrimordialSoup. The original code commented this as
"cheating"; nilled out. Only reached via SourceMirrorParser, which
is itself only instantiated by MirrorCache, which we lazify (see
below).
The bigger structural change is breaking the `||` simultaneous-slot
dependency between the four sub-modules:
- VCSLib's VCS class used `|| ... ||` so each sub-module's slot
block could read its siblings via vcs.<other-module>. That
machinery wants a Future class in scope that VCSLib doesn't
have; the resulting submodule load fails with
`MessageNotUnderstood: VCS Future`.
- Fix: every cross-module slot-block binding moves to a
`lazy public` slot in the module body. Each module captures vcs
in a `vcsHolder` slot and reads vcs.<sibling>.<X> lazily on
first method-time access — by which point every module's slot
block has finished. The four sub-modules in VCS itself are
similarly lazy public, so VCS instance creation no longer
triggers any sub-module construction.
Affected lazy bindings:
- VCSCore: importedSourceMirrors, Differencer
- VCSSourceMirrors: Differencer, newspeakSourceMirrors
- VCSNewspeakSourceMirrors: SourceMirror, StringMirror,
compilationUnitCache, methodDeclCache
Other small clean-ups:
- Logger>>logMonitor slot dropped; the only reference was in a
commented-out critical: section and a Monitor primitive isn't
needed in the single-threaded browser anyway.
- VCSLib manifest slimmed: VCSCore + VCSDiffing + VCSSourceMirrors
+ VCSNewspeakSourceMirrors only. UI, the three concrete backends
(Git / Mercurial / Subversion, all of which shell out via
ExternalLauncher), SmalltalkSourceMirrors, and
VCSImageMirrorCaching are dropped from the active manifest and
will return in later commits — most likely with a brand-new
VCSIsomorphicGitBackendProvider replacing the Squeak-CLI git
backend, since shelling out doesn't translate to the browser.
Test scaffolding:
- MemoryHoleTesting: a TestContext with six smoke tests that
instantiate VCSLib via packageUsing: ideNamespace, build a VCS
against a minimal IDEStub, and confirm each public sub-module
accessor returns non-nil. Forces every slot block to evaluate,
plus the lazy cross-module bindings to fire when each
sub-module's getter is called.
- MemoryHoleTestingConfiguration: standard packageTestsUsing:
factory. Builds the VCSLib instance at config-init using
ideNamespace itself as the manifest (since the IDE namespace
exposes top-level classes by name). IDEStub + NamespacingStub
nested classes provide the minimal ide.namespacing.Root surface
the slot blocks read.
- tool/run-tests.sh: accepts extra .ns dep files after the test-
config argument. The auto-discovery only looks for one IMPL_FILE
matching TEST_BASE; multi-file packages like MemoryHole (six
.ns files) need the dep list passed in explicitly.
All six tests pass via:
./run-tests.sh MemoryHoleTestingConfiguration \\
VCSLib.ns VCSCore.ns VCSDiffing.ns \\
VCSSourceMirrors.ns VCSNewspeakSourceMirrors.ns
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the MemoryHole port (see SESSION_SUMMARY_2026-05-20_MEMHOLE.md outstanding item #2). The compilationUnitCache / methodDeclCache lazy slots in VCSNewspeakSourceMirrors are now functional, and an end-to-end smoke test parses a minimal toplevel class through them. - TypedNS3Grammar sourced via `ide grammar` (HopscotchWebIDE already exposes a NewspeakGrammar instance), consistent with how Root is fetched via `ide namespacing`. - InMemoryMirrorCache replaces the nilled imageMirrorCache; per- production string->mirror map, lives for the module's lifetime. No image-backed persistence (the Squeak-era cache is gone). - MirrorCache no longer uses `parser perform: selector` (primordialsoup confines perform: to mirrors). Takes a productionFromParser: block instead; the selector is still kept as the cache key. - mirrorForString: added as the stream-free entry point on both MirrorCache and the outer module. mirrorForStream: delegates. - isCollection -> isKindOfCollection (3 sites). - classDecl wrapper extended to 7 args, matching the grammar's typeParameterDecl opt slot. The new param is accepted and ignored for now (mirror layer doesn't yet model generic class params). - SourceReference>>contents simplified to string-only; the Squeak stream-handling branch is dropped. Tests: MemoryHoleTesting gains SourceMirrorParserTests with three checks - the two lazy caches load, and testParseMinimalToplevelClass parses `class Foo = ( ) ( ) : ( )` through mirrorForString: and asserts the resulting ClassSourceMirror's name and category. MemoryHoleTestingConfiguration's IDEStub extended to provide a real `grammar` accessor (NewspeakGrammar usingPlatform: parsers: CombinatorialParsing) built from the test namespace. Depends on the ifNotNil: / isNil family additions to _Object in primordialsoup (commit 5f7699e). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds VCSLib + the four phase-1 sub-modules (VCSCore, VCSDiffing, VCSSourceMirrors, VCSNewspeakSourceMirrors) and the MemoryHole testing modules to HopscotchWebIDE, so they're reachable from the workspace and picked up by the test runner alongside other configurations. - private manifest imports for the five MemoryHole modules. - testModules collection extended with MemoryHoleTesting and MemoryHoleTestingConfiguration (the latter is what the IDE recognizes as a runnable configuration via packageTestsUsing:). - Explicit at:put: entries in both populateNamespaceUsingPlatform: and namespaceGivenPlatform: for the five modules. Archived Squeak-era pieces (Git/Hg/Svn launcher backends, SmalltalkSourceMirrors, VCSImageMirrorCaching, VCSUI) are deliberately not exposed; they have unported Squeak deps and aren't in VCSLib's phase-1 manifest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First piece of the MemoryHole git backend port. Proves the JS bridge
end-to-end: a Backend can do local repo ops (init, add, commit, log)
and remote ops (clone via HTTP smart protocol through a CORS proxy),
all chained via the alien Promise bridge.
Module shape (intentionally MVP scope):
- Backend pairs the isomorphic-git facade with one lightning-fs
instance scoped to the 'newspeak-memhole' IndexedDB store.
- smokeTest exercises mkdir -> init -> writeFile -> add -> commit ->
log against a local dir, resolving to a String summary.
- cloneFrom: clones a remote URL into cloneDir via a CORS proxy. The
corsProxy slot is mutable; defaults to http://localhost:9999 for
dev. The deployed IDE will need a project-hosted proxy on the same
origin so end-users don't run their own.
- Class-side smokeTestUsingPlatform: and
cloneSmokeTestUsingPlatform:url:[corsProxy:] variants for one-line
workspace invocation.
The AbstractBackend / AbstractRepository / AbstractHistorian lift is
deferred to a follow-up commit. That work has to reshape the abstract
interface from synchronous (Squeak shell-outs) to Promise-based
(isomorphic-git is fundamentally async).
Exposed in the IDE namespace via HopscotchWebIDE so it's reachable
from a workspace.
Dev-side dependency: a CORS proxy is required for any clone/fetch/push
to GitHub (GitHub's git smart-HTTP endpoints don't send CORS headers).
tool/cors-proxy.py is a stdlib-only Python proxy reproducing
@isomorphic-git/cors-proxy semantics. Run via
python3 tool/cors-proxy.py
(default port 9999). Standalone, no Node, no Homebrew, no Docker.
Smoke-test invocations from an IDE workspace:
(VCSIsomorphicGitBackendProvider smokeTestUsingPlatform: platform)
then: [:summary | summary out].
(VCSIsomorphicGitBackendProvider
cloneSmokeTestUsingPlatform: platform
url: 'https://github.com/octocat/Hello-World')
then: [:summary | summary out].
Depends on the isomorphic-git / lightning-fs / GitHttp <script> tags
landing in primordialsoup/meta/shell.html.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tracked newspeak/primordialsoup.html was behind newspeak/out/ primordialsoup.html after the isomorphic-git + lightning-fs + http/web <script> tags landed. The build chain regenerates out/primordialsoup.html from primordialsoup/meta/shell.html on each rebuild, but the tracked root copy isn't on that path and was left stale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets the AI ask the user to pick a file via the OS file picker; the
file's contents come back to the model in tool_result.content as one
of three Anthropic content-block shapes:
- image/* -> image block, source.type 'base64'
- application/pdf -> document block, source.type 'base64'
- everything else -> text block (file is read as text)
Capability-gated. Provider gets a new supportsFileUploads predicate
(default false; AnthropicProvider returns true) and tools are now
built per-provider via toolsForProvider: rather than the flat tools
list. Providers that can't carry image/document blocks don't get the
tool offered.
Session and document plumbing for user-side attachments:
- Provider#addUserText:documents:toMessages: builds a user content
array with the message text plus one document block per attached
file (each a text-source document for now; binary attachments are
delivered by the tool, not by the user message).
- Session#sendMessage:attachments: routes through it.
- IDEChatDocument#sendText:attachments: and ChatSubject#sendText:
attachments: forward attachments down the chain.
FileChooserFragment gains cancel handling. chooseFileList:ifCanceled:
and chooseSingleFile:ifCanceled: wire the <input type=file> 'cancel'
event (Chrome 113+, Firefox 91+, Safari 16.4+) so the tool can reject
its promise when the user dismisses the dialog instead of hanging.
Mechanical rename in AIAccess and AI_IDE_Support: the
`retainedPlatform = platform` slot becomes `js = platform js`, and
the call sites read `js localStorage` / `js global` directly. The
previous slot name was misleading (it implied "retained across the
snapshot freeze" but the only usage was platform.js) and the new
form is shorter at the call sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Primordialsoup's alien bridge does not pass arguments to a Newspeak closure invoked synchronously by the JS Promise constructor — both resolve and reject arrived as nil, so the executor pattern `Promise new: [:resolve :reject | ...]` was unusable. Both pickFile and readFileAsBase64: now use Promise.withResolvers() and invoke the returned function aliens via Function.prototype.call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A sibling of Backend in VCSIsomorphicGitBackendProvider, dedicated to
recording IDE Accepts as commits. Owns its own lightning-fs instance
scoped to a named IndexedDB store so its objects don't intermingle
with other repos.
API:
recordSnapshot: files <Map[String, String]> message: msg
Writes each filename -> content, git-adds them, commits with the
given message and the canned 'IDE Drafts' author. Resolves to the
new commit SHA.
history
Resolves to git.log's JS array of commit objects (newest first).
snapshotAt: sha
Resolves to a JS array of [filename, content] pairs reconstructing
the tree at that commit. Returns the raw pair array rather than
a Newspeak Map: returning Newspeak collections from inside JS
Promise then: closures doesn't round-trip cleanly through the
alien bridge (the outer chain gets back garbage).
ensureInitialized is idempotent and cached - first access does mkdir +
git init, subsequent calls short-circuit via Promise.resolve.
Files are opaque: this class doesn't know that the contents are
Newspeak class sources. The IDE side decides the path scheme and
serializes mirrors into the (filename -> content) Map handed to
recordSnapshot:.
Class-side draftsSmokeTestUsingPlatform: records two snapshots, reads
the log, reconstructs the first snapshot; resolves to a summary
string. Uses its own 'newspeak-drafts-smoketest' store so it doesn't
interfere with any IDE-side drafts state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every successful Accept on installFromBuilders:into: now writes the
touched class sources to a hidden drafts repo and commits. Both
editor-side accepts and AI apply_changeset go through this method,
so a single hook covers both paths.
- New lazy public 'drafts' slot constructs the DraftsRepo on first
access (typically the first Accept), backed by the
'newspeak-ide-drafts' IndexedDB store at /drafts. Survives across
reloads. Lazy so sessions that never edit pay no cost and so the
isomorphic-git / lightning-fs <script> tags are guaranteed loaded
by first use.
- New 'platformHolder = p.' slot captures the factory parameter so the
lazy slot's initializer (which runs at first access, not at instance
construction) can reach the platform.
- New recordAcceptForBuilders: builds a filename -> source Map keyed
by '<ClassName>.ns' and fires drafts.recordSnapshot: with the
message 'Accept: <names>'. Promise is intentionally not awaited;
the Accept completes synchronously and the commit lands in the
background. Rejections log to the console via 'out' but don't fail
the install.
- installFromBuilders:into: calls recordAcceptForBuilders: after the
install + the existing localStorage backup completes, so the drafts
commit only fires for successful installs.
Inspect history from a workspace:
ide drafts history then: [:log |
0 to: (log at: 'length') - 1 do: [:i |
((log at: i) at: 'oid'), ' - ',
(((log at: i) at: 'commit') at: 'message')) out]]
Bootstrap restore (loadFrom:usingPlatform:) bypasses
installFromBuilders: and uses atomicInstaller directly, so reloads
don't generate a spurious "Accept" commit on startup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tool handlers may now resolve to a {text, media} JS object (or a plain
String for text-only, back-compat). Session normalizes both shapes,
then calls the new makeToolResult:textResult:media:isError: on the
provider. The per-call return is opaque to Session and folded into
messages by addToolResults:toMessages:.
Anthropic returns one tool_result block whose content array is a text
block followed by image/document blocks per supported media (image/*,
application/pdf). Audio/video are dropped with a notice appended to
the text so the model sees the omission rather than silent data loss.
Gemini returns an array of parts: a functionResponse part plus one
inlineData sibling per supported media (image/*, application/pdf,
audio/*, video/*). addToolResults:toMessages: flattens the per-call
arrays into a single user-role message's parts list. Filenames are
surfaced via an "Attached files: ..." line appended to the response
text, since Gemini's inlineData has no native title slot.
When a handler returns only media (text is empty), Session substitutes
"See attached" so both providers receive non-empty text.
upload_file is rewritten: fileToContentBlock: is replaced by
fileToToolResult: which builds the canonical {text, media} shape.
MIME-to-native-block dispatch moves out of the tool and into the
provider, where each provider knows its own capabilities.
GeminiProvider supportsFileUploads flipped to true. The upload_file
tool is now offered to Gemini sessions.
This is option 1 of the cross-provider media design (structured handler
return). The opaque-return contract leaves room for a future
OpenAI-compatible provider to emit a tool message plus a follow-up
user-role message carrying media (since OpenAI's tool channel doesn't
accept binary content) without further refactor of Session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
No description provided.