Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# mycoder

## 0.0.14

### Patch Changes

- Add new version check, use sub-agents for context more freely.

## 0.0.13

### Patch Changes
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mycoder",
"description": "A command line tool using agent that can do arbitrary tasks, including coding tasks",
"version": "0.0.13",
"version": "0.0.14",
"type": "module",
"bin": "./bin/cli.js",
"main": "./dist/index.js",
Expand Down Expand Up @@ -52,6 +52,7 @@
"chalk": "^5",
"dotenv": "^16",
"source-map-support": "^0.5",
"uuid": "^11.0.5",
"yargs": "^17",
"yargs-file-commands": "^0.0.17",
"zod": "^3",
Expand All @@ -61,6 +62,7 @@
"@changesets/cli": "^2",
"@eslint/js": "^9",
"@types/node": "^18",
"@types/uuid": "^10.0.0",
"@types/yargs": "^17",
"@typescript-eslint/eslint-plugin": "^8.23.0",
"@typescript-eslint/parser": "^8.23.0",
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions src/tools/getTools.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { subAgentTool } from "../tools/interaction/subAgent.js";
import { shellExecuteTool } from "../tools/system/shellExecute.js";
import { readFileTool } from "../tools/io/readFile.js";
import { userPromptTool } from "../tools/interaction/userPrompt.js";
import { sequenceCompleteTool } from "../tools/system/sequenceComplete.js";
import { fetchTool } from "../tools/io/fetch.js";
import { Tool } from "../core/types.js";
import { updateFileTool } from "./io/updateFile.js";
import { shellStartTool } from "./system/shellStart.js";
import { shellMessageTool } from "./system/shellMessage.js";

export async function getTools(): Promise<Tool[]> {
return [
subAgentTool,
readFileTool,
updateFileTool,
shellExecuteTool,
//shellExecuteTool, - remove for now.
userPromptTool,
sequenceCompleteTool,
fetchTool,
shellStartTool,
shellMessageTool,
] as Tool[];
}
186 changes: 186 additions & 0 deletions src/tools/system/shellMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { processStates, shellStartTool } from "./shellStart.js";
import { MockLogger } from "../../utils/mockLogger.js";
import { shellMessageTool } from "./shellMessage.js";

// eslint-disable-next-line max-lines-per-function
describe("shellMessageTool", () => {
const mockLogger = new MockLogger();

let testInstanceId = "";

beforeEach(() => {
processStates.clear();
});

afterEach(() => {
for (const processState of processStates.values()) {
processState.process.kill();
}
processStates.clear();
});

it("should interact with a running process", async () => {
// Start a test process
const startResult = await shellStartTool.execute(
{
command: "cat", // cat will echo back input
description: "Test interactive process",
},
{ logger: mockLogger }
);

testInstanceId = startResult.instanceId;

// Send input and get response
const result = await shellMessageTool.execute(
{
instanceId: testInstanceId,
stdin: "hello world",
description: "Test interaction",
},
{ logger: mockLogger }
);

expect(result.stdout).toBe("hello world");
expect(result.stderr).toBe("");
expect(result.completed).toBe(false);
});

it("should handle nonexistent process", async () => {
const result = await shellMessageTool.execute(
{
instanceId: "nonexistent-id",
description: "Test invalid process",
},
{ logger: mockLogger }
);

expect(result.error).toBeDefined();
expect(result.completed).toBe(false);
});

it("should handle process completion", async () => {
// Start a quick process
const startResult = await shellStartTool.execute(
{
command: 'echo "test" && exit',
description: "Test completion",
},
{ logger: mockLogger }
);

// Wait a moment for process to complete
await new Promise((resolve) => setTimeout(resolve, 100));

const result = await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
description: "Check completion",
},
{ logger: mockLogger }
);

expect(result.completed).toBe(true);
// Process should still be in processStates even after completion
expect(processStates.has(startResult.instanceId)).toBe(true);
});

it("should handle SIGTERM signal correctly", async () => {
// Start a long-running process
const startResult = await shellStartTool.execute(
{
command: "sleep 10",
description: "Test SIGTERM handling",
},
{ logger: mockLogger }
);

const result = await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
signal: "SIGTERM",
description: "Send SIGTERM",
},
{ logger: mockLogger }
);
expect(result.signaled).toBe(true);

await new Promise((resolve) => setTimeout(resolve, 100));

const result2 = await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
description: "Check on status",
},
{ logger: mockLogger }
);

expect(result2.completed).toBe(true);
expect(result2.error).toBeUndefined();
});

it("should handle signals on terminated process gracefully", async () => {
// Start a process
const startResult = await shellStartTool.execute(
{
command: "sleep 1",
description: "Test signal handling on terminated process",
},
{ logger: mockLogger }
);

// Wait for process to complete
await new Promise((resolve) => setTimeout(resolve, 1500));

// Try to send signal to completed process
const result = await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
signal: "SIGTERM",
description: "Send signal to terminated process",
},
{ logger: mockLogger }
);

expect(result.error).toBeDefined();
expect(result.signaled).toBe(false);
expect(result.completed).toBe(true);
});

it("should verify signaled flag after process termination", async () => {
// Start a process
const startResult = await shellStartTool.execute(
{
command: "sleep 5",
description: "Test signal flag verification",
},
{ logger: mockLogger }
);

// Send SIGTERM
await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
signal: "SIGTERM",
description: "Send SIGTERM",
},
{ logger: mockLogger }
);

await new Promise((resolve) => setTimeout(resolve, 300));

// Check process state after signal
const checkResult = await shellMessageTool.execute(
{
instanceId: startResult.instanceId,
description: "Check signal state",
},
{ logger: mockLogger }
);

expect(checkResult.signaled).toBe(true);
expect(checkResult.completed).toBe(true);
expect(processStates.has(startResult.instanceId)).toBe(true);
});
});
Loading