workspace mode management; drone status message socket events added
This commit is contained in:
parent
d92d61024a
commit
c5e5d16a51
@ -8,5 +8,8 @@
|
|||||||
"build": "vite build"
|
"build": "vite build"
|
||||||
},
|
},
|
||||||
"author": "Robert Colbert <rob.colbert@openplatform.us>",
|
"author": "Robert Colbert <rob.colbert@openplatform.us>",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"slug": "^11.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import slug from "slug";
|
||||||
import type { User, Project } from "../lib/api";
|
import type { User, Project } from "../lib/api";
|
||||||
import {
|
import {
|
||||||
projectApi,
|
projectApi,
|
||||||
@ -114,13 +115,125 @@ function NewProjectForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditProjectFormProps {
|
||||||
|
project: Project;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditProjectForm({
|
||||||
|
project,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
}: EditProjectFormProps) {
|
||||||
|
const [name, setName] = useState(project.name);
|
||||||
|
const [slugValue, setSlugValue] = useState(project.slug);
|
||||||
|
const [gitUrl, setGitUrl] = useState(project.gitUrl || "");
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!name || !slugValue) {
|
||||||
|
setError("Name and slug are required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
setError("");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await projectApi.update(project._id, {
|
||||||
|
name,
|
||||||
|
slug: slug(slugValue),
|
||||||
|
gitUrl: gitUrl || undefined,
|
||||||
|
});
|
||||||
|
onSuccess();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to update project");
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-lg">
|
||||||
|
<h2 className="text-xl font-semibold mb-6">Edit Project</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-text-secondary mb-1">
|
||||||
|
Project Name *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none"
|
||||||
|
placeholder="My Project"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-text-muted mt-1">
|
||||||
|
Used for display purposes only
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-text-secondary mb-1">
|
||||||
|
Project Slug *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={slugValue}
|
||||||
|
onChange={(e) => setSlugValue(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none"
|
||||||
|
placeholder="my-project"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-text-muted mt-1">
|
||||||
|
Unique identifier for the project directory. Changing this will
|
||||||
|
affect the workspace directory name on drones.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-text-secondary mb-1">
|
||||||
|
Git Repository URL
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={gitUrl}
|
||||||
|
onChange={(e) => setGitUrl(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none"
|
||||||
|
placeholder="https://github.com/user/repo.git"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||||
|
<div className="flex gap-3 pt-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={submitting}
|
||||||
|
className="px-4 py-2 bg-brand text-white rounded hover:bg-red-700 transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{submitting ? "Updating..." : "Save Changes"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
className="px-4 py-2 border border-border-default text-text-secondary rounded hover:bg-bg-tertiary transition-colors"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface ProjectInspectorProps {
|
interface ProjectInspectorProps {
|
||||||
project: Project;
|
project: Project;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
onUpdate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProjectInspector({ project, onDelete }: ProjectInspectorProps) {
|
function ProjectInspector({ project, onDelete, onUpdate }: ProjectInspectorProps) {
|
||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (
|
if (
|
||||||
@ -145,63 +258,82 @@ function ProjectInspector({ project, onDelete }: ProjectInspectorProps) {
|
|||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
<h2 className="text-xl font-semibold mb-6">Project Inspector</h2>
|
<h2 className="text-xl font-semibold mb-6">Project Inspector</h2>
|
||||||
<div className="space-y-6">
|
{editing ? (
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<EditProjectForm
|
||||||
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
project={project}
|
||||||
<div className="text-sm text-text-muted mb-1">Name</div>
|
onCancel={() => setEditing(false)}
|
||||||
<div className="text-text-primary font-medium">
|
onSuccess={() => {
|
||||||
{project.name}
|
setEditing(false);
|
||||||
|
onUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
||||||
|
<div className="text-sm text-text-muted mb-1">Name</div>
|
||||||
|
<div className="text-text-primary font-medium">
|
||||||
|
{project.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
||||||
|
<div className="text-sm text-text-muted mb-1">Slug</div>
|
||||||
|
<div className="font-mono text-text-primary">{project.slug}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
||||||
|
<div className="text-sm text-text-muted mb-1">Git URL</div>
|
||||||
|
<div className="text-text-primary font-mono text-sm break-all">
|
||||||
|
{project.gitUrl || (
|
||||||
|
<span className="text-text-muted">Not configured</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
||||||
|
<div className="text-sm text-text-muted mb-1">Status</div>
|
||||||
|
<div className="text-text-primary capitalize">
|
||||||
|
<span
|
||||||
|
className={`inline-block w-2 h-2 rounded-full mr-2 ${
|
||||||
|
project.status === "active"
|
||||||
|
? "bg-green-500"
|
||||||
|
: project.status === "inactive"
|
||||||
|
? "bg-yellow-500"
|
||||||
|
: "bg-gray-500"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{project.status}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
||||||
|
<div className="text-sm text-text-muted mb-1">Created</div>
|
||||||
|
<div className="text-text-primary">
|
||||||
|
{new Date(project.createdAt).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 border-t border-border-subtle flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
className="px-4 py-2 bg-brand text-white rounded hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
Edit Project
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={deleting}
|
||||||
|
className="px-4 py-2 border border-red-600 text-red-500 rounded hover:bg-red-900/20 transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{deleting ? "Deleting..." : "Delete Project"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
</>
|
||||||
<div className="text-sm text-text-muted mb-1">Slug</div>
|
)}
|
||||||
<div className="font-mono text-text-primary">{project.slug}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
|
||||||
<div className="text-sm text-text-muted mb-1">Git URL</div>
|
|
||||||
<div className="text-text-primary font-mono text-sm break-all">
|
|
||||||
{project.gitUrl || (
|
|
||||||
<span className="text-text-muted">Not configured</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
|
||||||
<div className="text-sm text-text-muted mb-1">Status</div>
|
|
||||||
<div className="text-text-primary capitalize">
|
|
||||||
<span
|
|
||||||
className={`inline-block w-2 h-2 rounded-full mr-2 ${
|
|
||||||
project.status === "active"
|
|
||||||
? "bg-green-500"
|
|
||||||
: project.status === "inactive"
|
|
||||||
? "bg-yellow-500"
|
|
||||||
: "bg-gray-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
{project.status}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 bg-bg-secondary border border-border-default rounded">
|
|
||||||
<div className="text-sm text-text-muted mb-1">Created</div>
|
|
||||||
<div className="text-text-primary">
|
|
||||||
{new Date(project.createdAt).toLocaleDateString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-6 border-t border-border-subtle">
|
|
||||||
<button
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={deleting}
|
|
||||||
className="px-4 py-2 border border-red-600 text-red-500 rounded hover:bg-red-900/20 transition-colors disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{deleting ? "Deleting..." : "Delete Project"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -685,6 +817,10 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
|||||||
navigate("/projects");
|
navigate("/projects");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProjectUpdated = () => {
|
||||||
|
loadProjects();
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectDrone = (drone: DroneRegistration) => {
|
const handleSelectDrone = (drone: DroneRegistration) => {
|
||||||
setSelectedDrone(drone);
|
setSelectedDrone(drone);
|
||||||
};
|
};
|
||||||
@ -795,6 +931,7 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
|||||||
<ProjectInspector
|
<ProjectInspector
|
||||||
project={selectedProject}
|
project={selectedProject}
|
||||||
onDelete={handleProjectDeleted}
|
onDelete={handleProjectDeleted}
|
||||||
|
onUpdate={handleProjectUpdated}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Right Sidebar - Drones & Chat Sessions */}
|
{/* Right Sidebar - Drones & Chat Sessions */}
|
||||||
|
|||||||
@ -229,6 +229,12 @@ importers:
|
|||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.5(@types/node@24.0.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.0.4)(esbuild@0.25.5)(jiti@2.6.1)(less@4.3.0)(tsx@4.21.0))
|
version: 4.1.5(@types/node@24.0.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.0.4)(esbuild@0.25.5)(jiti@2.6.1)(less@4.3.0)(tsx@4.21.0))
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
dependencies:
|
||||||
|
slug:
|
||||||
|
specifier: ^11.0.1
|
||||||
|
version: 11.0.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@adobe/css-tools@4.4.4':
|
'@adobe/css-tools@4.4.4':
|
||||||
@ -2714,6 +2720,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
slug@11.0.1:
|
||||||
|
resolution: {integrity: sha512-VrM060OM/E7rdLQSnp6JHrzFfJFmqQBp0+TMhZStnEB8PfNliaZ9UWYjTHGHLUFVJorZ8TjVd/aKvIxHWU2O7g==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
socket.io-adapter@2.5.5:
|
socket.io-adapter@2.5.5:
|
||||||
resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
|
resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
|
||||||
|
|
||||||
@ -5604,6 +5614,8 @@ snapshots:
|
|||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
|
slug@11.0.1: {}
|
||||||
|
|
||||||
socket.io-adapter@2.5.5:
|
socket.io-adapter@2.5.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.7
|
debug: 4.3.7
|
||||||
|
|||||||
@ -37,7 +37,10 @@ export class CodeSession extends SocketSession {
|
|||||||
super.register();
|
super.register();
|
||||||
|
|
||||||
this.socket.on("requestSessionLock", this.onRequestSessionLock.bind(this));
|
this.socket.on("requestSessionLock", this.onRequestSessionLock.bind(this));
|
||||||
this.socket.on("requestWorkspaceMode", this.onRequestWorkspaceMode.bind(this));
|
this.socket.on(
|
||||||
|
"requestWorkspaceMode",
|
||||||
|
this.onRequestWorkspaceMode.bind(this),
|
||||||
|
);
|
||||||
this.socket.on("submitPrompt", this.onSubmitPrompt.bind(this));
|
this.socket.on("submitPrompt", this.onSubmitPrompt.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +87,6 @@ export class CodeSession extends SocketSession {
|
|||||||
this.project = project;
|
this.project = project;
|
||||||
SocketService.registerChatSession(chatSession._id, this);
|
SocketService.registerChatSession(chatSession._id, this);
|
||||||
droneSession.setChatSessionId(chatSession._id);
|
droneSession.setChatSessionId(chatSession._id);
|
||||||
droneSession.setCodeSession(this);
|
|
||||||
}
|
}
|
||||||
cb(success, chatSessionId);
|
cb(success, chatSessionId);
|
||||||
},
|
},
|
||||||
@ -192,4 +194,8 @@ export class CodeSession extends SocketSession {
|
|||||||
this.workspaceMode = mode;
|
this.workspaceMode = mode;
|
||||||
this.socket.emit("workspaceModeChanged", mode);
|
this.socket.emit("workspaceModeChanged", mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStatus(content: string): void {
|
||||||
|
this.socket.emit("status", content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@ export class DroneSession extends SocketSession {
|
|||||||
chatSessionId: GadgetId | undefined;
|
chatSessionId: GadgetId | undefined;
|
||||||
currentTurnId: GadgetId | undefined;
|
currentTurnId: GadgetId | undefined;
|
||||||
workspaceMode: WorkspaceMode = WorkspaceMode.Idle;
|
workspaceMode: WorkspaceMode = WorkspaceMode.Idle;
|
||||||
codeSession: import("./code-session.js").CodeSession | undefined;
|
|
||||||
|
|
||||||
constructor(socket: GadgetSocket, registration: IDroneRegistration) {
|
constructor(socket: GadgetSocket, registration: IDroneRegistration) {
|
||||||
super(socket, registration.user as IUser);
|
super(socket, registration.user as IUser);
|
||||||
@ -33,16 +32,41 @@ export class DroneSession extends SocketSession {
|
|||||||
register() {
|
register() {
|
||||||
super.register();
|
super.register();
|
||||||
|
|
||||||
|
this.socket.on("status", this.onStatus.bind(this));
|
||||||
|
this.socket.on(
|
||||||
|
"workspaceModeChanged",
|
||||||
|
this.onWorkspaceModeChanged.bind(this),
|
||||||
|
);
|
||||||
|
|
||||||
this.socket.on("thinking", this.onThinking.bind(this));
|
this.socket.on("thinking", this.onThinking.bind(this));
|
||||||
this.socket.on("response", this.onResponse.bind(this));
|
this.socket.on("response", this.onResponse.bind(this));
|
||||||
this.socket.on("toolCall", this.onToolCall.bind(this));
|
this.socket.on("toolCall", this.onToolCall.bind(this));
|
||||||
|
|
||||||
this.socket.on("workOrderComplete", this.onWorkOrderComplete.bind(this));
|
this.socket.on("workOrderComplete", this.onWorkOrderComplete.bind(this));
|
||||||
|
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
"requestCrashRecovery",
|
"requestCrashRecovery",
|
||||||
this.onRequestCrashRecovery.bind(this),
|
this.onRequestCrashRecovery.bind(this),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.socket.on("requestTermination", this.onRequestTermination.bind(this));
|
this.socket.on("requestTermination", this.onRequestTermination.bind(this));
|
||||||
this.socket.on("workspaceModeChanged", this.onWorkspaceModeChanged.bind(this));
|
}
|
||||||
|
|
||||||
|
async onStatus(message: string): Promise<void> {
|
||||||
|
if (!this.chatSessionId) {
|
||||||
|
this.log.warn(
|
||||||
|
"drone status event received but no chat session is active",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const codeSession = SocketService.getCodeSessionByChatSessionId(
|
||||||
|
this.chatSessionId,
|
||||||
|
);
|
||||||
|
codeSession.socket.emit("status", message);
|
||||||
|
} catch (error) {
|
||||||
|
this.log.error("failed to route status message", { error });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,23 +207,22 @@ export class DroneSession extends SocketSession {
|
|||||||
this.currentTurnId = turnId;
|
this.currentTurnId = turnId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the associated code session for routing events back to the IDE.
|
|
||||||
*/
|
|
||||||
setCodeSession(codeSession: import("./code-session.js").CodeSession): void {
|
|
||||||
this.codeSession = codeSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the drone emits a workspace mode change.
|
* Called when the drone emits a workspace mode change.
|
||||||
*/
|
*/
|
||||||
async onWorkspaceModeChanged(mode: WorkspaceMode): Promise<void> {
|
async onWorkspaceModeChanged(mode: WorkspaceMode): Promise<void> {
|
||||||
|
if (!this.chatSessionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.workspaceMode = mode;
|
this.workspaceMode = mode;
|
||||||
this.log.info("workspace mode changed", { mode });
|
this.log.info("workspace mode changed", { mode });
|
||||||
|
|
||||||
if (this.codeSession) {
|
const codeSession = SocketService.getCodeSessionByChatSessionId(
|
||||||
this.codeSession.onWorkspaceModeChanged(mode);
|
this.chatSessionId,
|
||||||
}
|
);
|
||||||
|
|
||||||
|
codeSession.onWorkspaceModeChanged(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"ollama": "^0.6.3",
|
"ollama": "^0.6.3",
|
||||||
"openai": "^6.34.0",
|
"openai": "^6.34.0",
|
||||||
|
"simple-git": "^3.36.0",
|
||||||
"socket.io-client": "^4.8.3"
|
"socket.io-client": "^4.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -226,6 +226,8 @@ class GadgetDrone extends GadgetProcess {
|
|||||||
chatSession: IChatSession,
|
chatSession: IChatSession,
|
||||||
cb: RequestSessionLockCallback,
|
cb: RequestSessionLockCallback,
|
||||||
) {
|
) {
|
||||||
|
assert(this.socket, "invalid application state");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Validate gadget-drone registration to ensure correct sync with IDE
|
* Validate gadget-drone registration to ensure correct sync with IDE
|
||||||
*/
|
*/
|
||||||
@ -286,8 +288,45 @@ class GadgetDrone extends GadgetProcess {
|
|||||||
session: chatSession,
|
session: chatSession,
|
||||||
};
|
};
|
||||||
this.workspaceMode = WorkspaceMode.Idle;
|
this.workspaceMode = WorkspaceMode.Idle;
|
||||||
|
this.socket.emit("status", "session lock granted");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the project to the workspace, lock to it, and deploy it.
|
||||||
|
*/
|
||||||
|
WorkspaceService.addProject({
|
||||||
|
_id: project._id,
|
||||||
|
slug: project.slug,
|
||||||
|
gitUrl: project.gitUrl,
|
||||||
|
});
|
||||||
|
WorkspaceService.updateLockedProject({
|
||||||
|
_id: project._id,
|
||||||
|
slug: project.slug,
|
||||||
|
gitUrl: project.gitUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectDir = WorkspaceService.getProjectDirectory(project.slug);
|
||||||
|
|
||||||
|
let haveProjectDir = await WorkspaceService.exists(projectDir);
|
||||||
|
if (!haveProjectDir) {
|
||||||
|
this.socket.emit("status", `deploying project [slug=${project.slug}]`);
|
||||||
|
await WorkspaceService.deployProject(project);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure a project directory got deployed
|
||||||
|
*/
|
||||||
|
haveProjectDir = await WorkspaceService.exists(projectDir);
|
||||||
|
if (!haveProjectDir) {
|
||||||
|
return cb(false, "failed to deploy project directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Commit the changes to the workspace.
|
||||||
|
*/
|
||||||
|
await WorkspaceService.writeWorkspaceData();
|
||||||
cb(true, chatSession._id);
|
cb(true, chatSession._id);
|
||||||
|
|
||||||
|
this.socket.emit("status", "session lock granted");
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRequestWorkspaceMode(
|
async onRequestWorkspaceMode(
|
||||||
@ -434,17 +473,29 @@ class GadgetDrone extends GadgetProcess {
|
|||||||
|
|
||||||
cb(true, "work order accepted"); // confirm that drone has the work order
|
cb(true, "work order accepted"); // confirm that drone has the work order
|
||||||
|
|
||||||
|
const workspaceDir =
|
||||||
|
WorkspaceService.workspaceData?.workspaceDir || process.cwd();
|
||||||
|
const projectDir = WorkspaceService.getProjectDirectory(project.slug);
|
||||||
|
process.chdir(projectDir);
|
||||||
|
|
||||||
this.isProcessingWorkOrder = true;
|
this.isProcessingWorkOrder = true;
|
||||||
try {
|
try {
|
||||||
|
this.socket.emit("status", "processing work order");
|
||||||
await AgentService.process(order, this.socket);
|
await AgentService.process(order, this.socket);
|
||||||
|
this.socket.emit("status", "work order processing finished");
|
||||||
await WorkspaceService.removeWorkOrderCache();
|
await WorkspaceService.removeWorkOrderCache();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
this.log.error("work order processing failed", {
|
this.log.error("work order processing failed", {
|
||||||
error: err.message,
|
error: err.message,
|
||||||
});
|
});
|
||||||
|
this.socket.emit(
|
||||||
|
"status",
|
||||||
|
`failed to process work order: ${(error as Error).message}`,
|
||||||
|
);
|
||||||
// Leave cache in place for recovery
|
// Leave cache in place for recovery
|
||||||
} finally {
|
} finally {
|
||||||
|
process.chdir(workspaceDir);
|
||||||
this.isProcessingWorkOrder = false;
|
this.isProcessingWorkOrder = false;
|
||||||
this.log.info("work order processing complete", {
|
this.log.info("work order processing complete", {
|
||||||
isProcessingWorkOrder: this.isProcessingWorkOrder,
|
isProcessingWorkOrder: this.isProcessingWorkOrder,
|
||||||
|
|||||||
@ -2,11 +2,43 @@
|
|||||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||||
// Licensed under the Apache License, Version 2.0
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
import assert from "node:assert";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
|
||||||
|
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git";
|
||||||
|
|
||||||
|
import { IChatTurn, IProject } from "@gadget/api";
|
||||||
|
|
||||||
import { GadgetService } from "../lib/service.ts";
|
import { GadgetService } from "../lib/service.ts";
|
||||||
import { IChatTurn } from "@gadget/api";
|
|
||||||
|
export interface WorkspaceChatSession {
|
||||||
|
_id: string; // MongoDB ChatSession._id
|
||||||
|
name: string; // Session name for display
|
||||||
|
lockedAt: string; // ISO 8601 timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceLockedProject {
|
||||||
|
_id: string; // MongoDB Project._id
|
||||||
|
slug: string; // Project slug (directory name)
|
||||||
|
gitUrl?: string; // Remote git URL
|
||||||
|
lockedAt: string; // ISO 8601 timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceProject {
|
||||||
|
_id: string;
|
||||||
|
slug: string;
|
||||||
|
gitUrl?: string;
|
||||||
|
clonedAt: string | null;
|
||||||
|
lastSyncAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceRegistration {
|
||||||
|
_id: string; // MongoDB DroneRegistration._id
|
||||||
|
status: string; // Current drone status
|
||||||
|
registeredAt: string; // ISO 8601 timestamp
|
||||||
|
}
|
||||||
|
|
||||||
export interface WorkspaceData {
|
export interface WorkspaceData {
|
||||||
workspaceId: string; // UUID v4, immutable once created
|
workspaceId: string; // UUID v4, immutable once created
|
||||||
@ -15,35 +47,16 @@ export interface WorkspaceData {
|
|||||||
workspaceDir: string; // Absolute path to workspace directory
|
workspaceDir: string; // Absolute path to workspace directory
|
||||||
|
|
||||||
// Active session state (null when idle)
|
// Active session state (null when idle)
|
||||||
chatSession: {
|
chatSession: WorkspaceChatSession | null;
|
||||||
_id: string; // MongoDB ChatSession._id
|
|
||||||
name: string; // Session name for display
|
|
||||||
lockedAt: string; // ISO 8601 timestamp
|
|
||||||
} | null;
|
|
||||||
|
|
||||||
// Project currently being worked on (null when idle)
|
// Project currently being worked on (null when idle)
|
||||||
lockedProject: {
|
lockedProject: WorkspaceLockedProject | null;
|
||||||
_id: string; // MongoDB Project._id
|
|
||||||
slug: string; // Project slug (directory name)
|
|
||||||
gitUrl: string; // Remote git URL
|
|
||||||
lockedAt: string; // ISO 8601 timestamp
|
|
||||||
} | null;
|
|
||||||
|
|
||||||
// All projects cloned into this workspace
|
// All projects cloned into this workspace
|
||||||
projects: Array<{
|
projects: Array<WorkspaceProject>;
|
||||||
_id: string;
|
|
||||||
slug: string;
|
|
||||||
gitUrl: string;
|
|
||||||
clonedAt: string;
|
|
||||||
lastSyncAt: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// Drone registration (updated each startup)
|
// Drone registration (updated each startup)
|
||||||
registration: {
|
registration: WorkspaceRegistration | null;
|
||||||
_id: string; // MongoDB DroneRegistration._id
|
|
||||||
status: string; // Current drone status
|
|
||||||
registeredAt: string; // ISO 8601 timestamp
|
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkOrderCache {
|
export interface WorkOrderCache {
|
||||||
@ -57,9 +70,12 @@ export interface WorkOrderCache {
|
|||||||
class WorkspaceService extends GadgetService {
|
class WorkspaceService extends GadgetService {
|
||||||
private gadgetDir: string = "";
|
private gadgetDir: string = "";
|
||||||
private cacheDir: string = "";
|
private cacheDir: string = "";
|
||||||
|
|
||||||
private workspaceFile: string = "";
|
private workspaceFile: string = "";
|
||||||
private _workspaceData: WorkspaceData | null = null;
|
private _workspaceData: WorkspaceData | null = null;
|
||||||
|
|
||||||
|
private repos = new Map<string, SimpleGit>();
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return "WorkspaceService";
|
return "WorkspaceService";
|
||||||
}
|
}
|
||||||
@ -111,7 +127,7 @@ class WorkspaceService extends GadgetService {
|
|||||||
*/
|
*/
|
||||||
private async loadOrCreateWorkspaceData(workspaceDir: string): Promise<void> {
|
private async loadOrCreateWorkspaceData(workspaceDir: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (await this.fileExists(this.workspaceFile)) {
|
if (await this.exists(this.workspaceFile)) {
|
||||||
// Load existing workspace
|
// Load existing workspace
|
||||||
const content = await fs.promises.readFile(this.workspaceFile, "utf-8");
|
const content = await fs.promises.readFile(this.workspaceFile, "utf-8");
|
||||||
this._workspaceData = JSON.parse(content) as WorkspaceData;
|
this._workspaceData = JSON.parse(content) as WorkspaceData;
|
||||||
@ -175,33 +191,58 @@ class WorkspaceService extends GadgetService {
|
|||||||
* Updates the locked project in workspace data.
|
* Updates the locked project in workspace data.
|
||||||
*/
|
*/
|
||||||
updateLockedProject(
|
updateLockedProject(
|
||||||
project: { _id: string; slug: string; gitUrl: string } | null,
|
project: {
|
||||||
|
_id: string; // MongoDB Project._id
|
||||||
|
slug: string; // Project slug (directory name)
|
||||||
|
gitUrl?: string; // Remote git URL
|
||||||
|
} | null,
|
||||||
): void {
|
): void {
|
||||||
if (!this._workspaceData) return;
|
if (!this._workspaceData) return;
|
||||||
|
if (!project) {
|
||||||
this._workspaceData.lockedProject = project
|
this._workspaceData.lockedProject = null;
|
||||||
? {
|
return;
|
||||||
...project,
|
}
|
||||||
lockedAt: new Date().toISOString(),
|
this._workspaceData.lockedProject = Object.assign(
|
||||||
}
|
{
|
||||||
: null;
|
lockedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
project,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a project to the workspace projects list.
|
* Adds a project to the workspace projects list.
|
||||||
*/
|
*/
|
||||||
addProject(project: { _id: string; slug: string; gitUrl: string }): void {
|
addProject(project: { _id: string; slug: string; gitUrl?: string }): void {
|
||||||
if (!this._workspaceData) return;
|
if (!this._workspaceData) return;
|
||||||
|
|
||||||
const existing = this._workspaceData.projects.find(
|
const existing = this._workspaceData.projects.find(
|
||||||
(p) => p.slug === project.slug,
|
(p) => p.slug === project.slug,
|
||||||
);
|
);
|
||||||
if (!existing) {
|
if (existing) {
|
||||||
this._workspaceData.projects.push({
|
existing._id = project._id;
|
||||||
...project,
|
if (project.gitUrl) {
|
||||||
clonedAt: new Date().toISOString(),
|
existing.gitUrl = project.gitUrl;
|
||||||
lastSyncAt: new Date().toISOString(),
|
} else {
|
||||||
});
|
delete existing.gitUrl;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._workspaceData.projects.push({
|
||||||
|
...project,
|
||||||
|
clonedAt: null,
|
||||||
|
lastSyncAt: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deployProject(project: IProject): Promise<void> {
|
||||||
|
assert(this.workspaceData, "workspace uninitialized");
|
||||||
|
|
||||||
|
if (project.gitUrl) {
|
||||||
|
const git: SimpleGit = this.getGitRepo(project.slug);
|
||||||
|
git.clone(project.gitUrl);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +299,7 @@ class WorkspaceService extends GadgetService {
|
|||||||
async removeWorkOrderCache(): Promise<void> {
|
async removeWorkOrderCache(): Promise<void> {
|
||||||
const cacheFile = path.join(this.cacheDir, "work-order.json");
|
const cacheFile = path.join(this.cacheDir, "work-order.json");
|
||||||
try {
|
try {
|
||||||
if (await this.fileExists(cacheFile)) {
|
if (await this.exists(cacheFile)) {
|
||||||
await fs.promises.unlink(cacheFile);
|
await fs.promises.unlink(cacheFile);
|
||||||
this.log.info("work order cache removed");
|
this.log.info("work order cache removed");
|
||||||
}
|
}
|
||||||
@ -276,7 +317,7 @@ class WorkspaceService extends GadgetService {
|
|||||||
async readWorkOrderCache(): Promise<WorkOrderCache | null> {
|
async readWorkOrderCache(): Promise<WorkOrderCache | null> {
|
||||||
const cacheFile = path.join(this.cacheDir, "work-order.json");
|
const cacheFile = path.join(this.cacheDir, "work-order.json");
|
||||||
try {
|
try {
|
||||||
if (await this.fileExists(cacheFile)) {
|
if (await this.exists(cacheFile)) {
|
||||||
const content = await fs.promises.readFile(cacheFile, "utf-8");
|
const content = await fs.promises.readFile(cacheFile, "utf-8");
|
||||||
return JSON.parse(content) as WorkOrderCache;
|
return JSON.parse(content) as WorkOrderCache;
|
||||||
}
|
}
|
||||||
@ -290,9 +331,9 @@ class WorkspaceService extends GadgetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a file exists.
|
* Checks if a file or directory exists.
|
||||||
*/
|
*/
|
||||||
private async fileExists(filePath: string): Promise<boolean> {
|
async exists(filePath: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(filePath);
|
await fs.promises.access(filePath);
|
||||||
return true;
|
return true;
|
||||||
@ -300,6 +341,35 @@ class WorkspaceService extends GadgetService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProjectDirectory(slug: string): string {
|
||||||
|
assert(this.workspaceData, "workspace uninitialized");
|
||||||
|
return path.join(this.workspaceData?.workspaceDir, slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGitRepo(slug: string): SimpleGit {
|
||||||
|
assert(this.workspaceData, "workspace uninitialized");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have a SimpleGit instance for the project slug, return it.
|
||||||
|
*/
|
||||||
|
let git: SimpleGit | undefined = this.repos.get(slug);
|
||||||
|
if (git) {
|
||||||
|
return git;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new SimpleGit instance rooted in the project's workspace
|
||||||
|
* directory, add it to the repo cache, and return it.
|
||||||
|
*/
|
||||||
|
const options: Partial<SimpleGitOptions> = {
|
||||||
|
baseDir: this.getProjectDirectory(slug),
|
||||||
|
};
|
||||||
|
git = simpleGit(options);
|
||||||
|
this.repos.set(slug, git);
|
||||||
|
|
||||||
|
return git;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkspaceService();
|
export default new WorkspaceService();
|
||||||
|
|||||||
@ -18,6 +18,8 @@ export type ProcessWorkOrderMessage = (
|
|||||||
cb: ProcessWorkOrderCallback,
|
cb: ProcessWorkOrderCallback,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
export type StatusMessage = (content: string) => void;
|
||||||
|
|
||||||
export type ThinkingMessage = (content: string) => void;
|
export type ThinkingMessage = (content: string) => void;
|
||||||
|
|
||||||
export type ResponseMessage = (content: string) => void;
|
export type ResponseMessage = (content: string) => void;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
StatusMessage,
|
||||||
ProcessWorkOrderMessage,
|
ProcessWorkOrderMessage,
|
||||||
ResponseMessage,
|
ResponseMessage,
|
||||||
ThinkingMessage,
|
ThinkingMessage,
|
||||||
@ -50,6 +51,7 @@ export interface ClientToServerEvents {
|
|||||||
* gadget-drone => gadget-code:web
|
* gadget-drone => gadget-code:web
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
status: StatusMessage;
|
||||||
thinking: ThinkingMessage;
|
thinking: ThinkingMessage;
|
||||||
response: ResponseMessage;
|
response: ResponseMessage;
|
||||||
toolCall: ToolCallMessage;
|
toolCall: ToolCallMessage;
|
||||||
@ -74,6 +76,7 @@ export interface ServerToClientEvents {
|
|||||||
* gadget-code:web => gadget-code:ide
|
* gadget-code:web => gadget-code:ide
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
status: StatusMessage;
|
||||||
thinking: ThinkingMessage;
|
thinking: ThinkingMessage;
|
||||||
response: ResponseMessage;
|
response: ResponseMessage;
|
||||||
toolCall: ToolCallMessage;
|
toolCall: ToolCallMessage;
|
||||||
|
|||||||
@ -287,6 +287,9 @@ importers:
|
|||||||
openai:
|
openai:
|
||||||
specifier: ^6.34.0
|
specifier: ^6.34.0
|
||||||
version: 6.34.0(ws@8.18.3)
|
version: 6.34.0(ws@8.18.3)
|
||||||
|
simple-git:
|
||||||
|
specifier: ^3.36.0
|
||||||
|
version: 3.36.0
|
||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.8.3
|
specifier: ^4.8.3
|
||||||
version: 4.8.3
|
version: 4.8.3
|
||||||
@ -943,6 +946,12 @@ packages:
|
|||||||
'@kurkle/color@0.3.4':
|
'@kurkle/color@0.3.4':
|
||||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||||
|
|
||||||
|
'@kwsites/file-exists@1.1.1':
|
||||||
|
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
|
||||||
|
|
||||||
|
'@kwsites/promise-deferred@1.1.1':
|
||||||
|
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
|
||||||
|
|
||||||
'@mediapipe/tasks-vision@0.10.17':
|
'@mediapipe/tasks-vision@0.10.17':
|
||||||
resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==}
|
resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==}
|
||||||
|
|
||||||
@ -1150,6 +1159,12 @@ packages:
|
|||||||
'@rolldown/pluginutils@1.0.0-rc.7':
|
'@rolldown/pluginutils@1.0.0-rc.7':
|
||||||
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
|
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
|
||||||
|
|
||||||
|
'@simple-git/args-pathspec@1.0.3':
|
||||||
|
resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==}
|
||||||
|
|
||||||
|
'@simple-git/argv-parser@1.1.1':
|
||||||
|
resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==}
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2':
|
'@socket.io/component-emitter@3.1.2':
|
||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
@ -3171,6 +3186,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
simple-git@3.36.0:
|
||||||
|
resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}
|
||||||
|
|
||||||
slash@3.0.0:
|
slash@3.0.0:
|
||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4103,6 +4121,14 @@ snapshots:
|
|||||||
|
|
||||||
'@kurkle/color@0.3.4': {}
|
'@kurkle/color@0.3.4': {}
|
||||||
|
|
||||||
|
'@kwsites/file-exists@1.1.1':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@kwsites/promise-deferred@1.1.1': {}
|
||||||
|
|
||||||
'@mediapipe/tasks-vision@0.10.17': {}
|
'@mediapipe/tasks-vision@0.10.17': {}
|
||||||
|
|
||||||
'@mongodb-js/saslprep@1.4.9':
|
'@mongodb-js/saslprep@1.4.9':
|
||||||
@ -4265,6 +4291,12 @@ snapshots:
|
|||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-rc.7': {}
|
'@rolldown/pluginutils@1.0.0-rc.7': {}
|
||||||
|
|
||||||
|
'@simple-git/args-pathspec@1.0.3': {}
|
||||||
|
|
||||||
|
'@simple-git/argv-parser@1.1.1':
|
||||||
|
dependencies:
|
||||||
|
'@simple-git/args-pathspec': 1.0.3
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2': {}
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
@ -6421,6 +6453,16 @@ snapshots:
|
|||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
|
simple-git@3.36.0:
|
||||||
|
dependencies:
|
||||||
|
'@kwsites/file-exists': 1.1.1
|
||||||
|
'@kwsites/promise-deferred': 1.1.1
|
||||||
|
'@simple-git/args-pathspec': 1.0.3
|
||||||
|
'@simple-git/argv-parser': 1.1.1
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
slug@11.0.1: {}
|
slug@11.0.1: {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user