bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
122 lines
4.9 KiB
Python
122 lines
4.9 KiB
Python
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
|
|
|
from mirage.io.types import ByteSource, IOResult
|
|
from mirage.resource.history import HISTORY_PREFIX
|
|
from mirage.workspace.session.session import Session
|
|
from mirage.workspace.types import ExecutionNode
|
|
|
|
USAGE = ("history: usage: history [-c] [-d offset] [n] or "
|
|
"history -awrn [filename] or history -ps arg [arg...]\n")
|
|
OPTION_CHARS = "cdanrwsp"
|
|
|
|
|
|
def _usage_error(message: str) -> tuple[None, IOResult, ExecutionNode]:
|
|
err = (message + USAGE).encode()
|
|
io = IOResult(exit_code=2, stderr=err)
|
|
return None, io, ExecutionNode(command="history", exit_code=2, stderr=err)
|
|
|
|
|
|
def _parse_args(
|
|
args: list[str]) -> tuple[dict[str, object], list[str], str | None]:
|
|
"""Parse history builtin args the way bash getopt does.
|
|
|
|
Args:
|
|
args (list[str]): Raw tokens after the command name.
|
|
|
|
Returns:
|
|
tuple[dict[str, object], list[str], str | None]: Flags dict,
|
|
operand texts, and an error message (None when parsing
|
|
succeeded). Any dash-leading token is option-parsed, digits
|
|
included (`history -1` is an invalid option in bash); `--` or
|
|
the first operand ends option parsing, so `history -s rm -rf`
|
|
stores "rm -rf" as text. `-d` takes the rest of its token as
|
|
the offset when attached (`-d3`, and `-dc` deletes entry "c"),
|
|
otherwise the next token.
|
|
"""
|
|
flags: dict[str, object] = {}
|
|
texts: list[str] = []
|
|
options_done = False
|
|
i = 0
|
|
while i < len(args):
|
|
token = args[i]
|
|
if options_done or token == "-" or not token.startswith("-"):
|
|
texts.append(token)
|
|
options_done = True
|
|
elif token == "--":
|
|
options_done = True
|
|
else:
|
|
j = 1
|
|
while j < len(token):
|
|
ch = token[j]
|
|
if ch not in OPTION_CHARS:
|
|
return {}, [], f"history: -{ch}: invalid option\n"
|
|
flags[ch] = True
|
|
if ch == "d":
|
|
rest = token[j + 1:]
|
|
if rest:
|
|
flags["d"] = rest
|
|
elif i + 1 < len(args):
|
|
i += 1
|
|
flags["d"] = args[i]
|
|
else:
|
|
return ({}, [],
|
|
"history: -d: option requires an argument\n")
|
|
break
|
|
j += 1
|
|
i += 1
|
|
return flags, texts, None
|
|
|
|
|
|
async def handle_history(
|
|
registry,
|
|
args: list[str],
|
|
session: Session,
|
|
) -> tuple[ByteSource | None, IOResult, ExecutionNode]:
|
|
"""Dispatch the history shell builtin to the view mount.
|
|
|
|
GNU lookup order: builtins resolve before mount commands, so a
|
|
mount-local command named "history" can never shadow this one.
|
|
The actual semantics live on the /.bash_history view resource;
|
|
this handler only parses options and routes.
|
|
|
|
Args:
|
|
registry (MountRegistry): The workspace's mount registry.
|
|
args (list[str]): Raw builtin args (flags and counts).
|
|
session (Session): Calling session.
|
|
"""
|
|
flags, texts, error = _parse_args(args)
|
|
if error is not None:
|
|
return _usage_error(error)
|
|
try:
|
|
mount = registry.mount_for(HISTORY_PREFIX)
|
|
except ValueError:
|
|
err = b"history: not enabled for this workspace\n"
|
|
return None, IOResult(exit_code=1,
|
|
stderr=err), ExecutionNode(command="history",
|
|
exit_code=1,
|
|
stderr=err)
|
|
stream, io = await mount.execute_cmd("history", [],
|
|
texts,
|
|
flags,
|
|
cwd=session.cwd,
|
|
session_id=session.session_id)
|
|
# The view command always returns byte stderr, but io.stderr is typed
|
|
# as a ByteSource (a possible lazy stream); resolve it to bytes so the
|
|
# execution-tree node holds concrete stderr, never an unread stream.
|
|
stderr = await io.materialize_stderr()
|
|
return stream, io, ExecutionNode(command="history",
|
|
exit_code=io.exit_code,
|
|
stderr=stderr)
|