heyio 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/api/server.js +11 -4
- package/dist/chat/attachments.js +76 -0
- package/dist/copilot/agents.js +37 -3
- package/dist/copilot/ceremonies.js +13 -6
- package/dist/copilot/orchestrator.js +23 -5
- package/dist/copilot/squad-tools.js +18 -1
- package/dist/copilot/system-message.js +4 -0
- package/dist/copilot/tools.js +43 -5
- package/dist/store/conversations.js +14 -2
- package/dist/store/db.js +7 -0
- package/package.json +1 -1
- package/web-dist/assets/{AuditLogView-eyz02XQx.js → AuditLogView-Ds7LwE01.js} +1 -1
- package/web-dist/assets/ChatView-90GHN5-3.js +11 -0
- package/web-dist/assets/{FeedView-Clis71Lg.js → FeedView-RlM4m0Dp.js} +1 -1
- package/web-dist/assets/{HistoryView-B1EJ2Q9S.js → HistoryView-Vrb5nk4l.js} +1 -1
- package/web-dist/assets/{LoginView-bg8Sl_Gw.js → LoginView-BVt0eVoK.js} +1 -1
- package/web-dist/assets/{McpView-CBepc2JJ.js → McpView-DMg1Ieji.js} +1 -1
- package/web-dist/assets/{SchedulesView-DDFUUMPG.js → SchedulesView-CvasMUhn.js} +1 -1
- package/web-dist/assets/{SettingsView-BFfDRToB.js → SettingsView-DqNi_5IM.js} +1 -1
- package/web-dist/assets/{SkillsView-Dv-337Yq.js → SkillsView-CufZGrhf.js} +1 -1
- package/web-dist/assets/{SquadDetailView-p4c6XQ5a.js → SquadDetailView-DmseEdEl.js} +1 -1
- package/web-dist/assets/{SquadHealthView-IjOdIla7.js → SquadHealthView-D4kmk8OD.js} +1 -1
- package/web-dist/assets/{SquadsView-C5wQPkBA.js → SquadsView-CVB2pCwd.js} +1 -1
- package/web-dist/assets/{UsageView-BcHfPX-N.js → UsageView-xEe1ZUp4.js} +1 -1
- package/web-dist/assets/{WikiView-CKBw5FOf.js → WikiView-ADnv-80y.js} +7 -12
- package/web-dist/assets/{api-DPUP4xY9.js → api-2XP2yG1A.js} +1 -1
- package/web-dist/assets/{arrow-left-BMJZvMdZ.js → arrow-left-BsbWSKAE.js} +1 -1
- package/web-dist/assets/file-text-Dn2yDZCL.js +6 -0
- package/web-dist/assets/{git-branch-BF40208L.js → git-branch-wkQUgAGS.js} +1 -1
- package/web-dist/assets/{index-CQ_szaoT.css → index-Cd9kiyp4.css} +1 -1
- package/web-dist/assets/{index-DSAUDIwK.js → index-DI8QgDXv.js} +35 -35
- package/web-dist/assets/{plus-aHVgAXQB.js → plus-BOXwfDpA.js} +1 -1
- package/web-dist/assets/{save-jn5LGsCv.js → save-CTfFmP-U.js} +1 -1
- package/web-dist/assets/{search-DUqJxRNn.js → search-BnsxENnH.js} +1 -1
- package/web-dist/assets/{trash-2-B9ibnt3n.js → trash-2-C86P23n5.js} +1 -1
- package/web-dist/assets/{triangle-alert-Cindqw1C.js → triangle-alert-MLnkHsRL.js} +1 -1
- package/web-dist/assets/{x-BDIsved4.js → x-Cw7ZjfeL.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/ChatView-DXpE1NMj.js +0 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ IO is a personal AI assistant daemon built on the GitHub Copilot SDK. It runs 24
|
|
|
11
11
|
- **Copilot SDK Integration** — powered by GitHub's Copilot SDK for LLM conversations with tool calling
|
|
12
12
|
- **Multi-Interface** — Web dashboard + Telegram bot + HTTP API
|
|
13
13
|
- **Web Frontend** — Vue 3 dashboard with chat, squad management, skills, and agent activity views
|
|
14
|
+
- **Web Chat Attachments** — Attach images/files in web chat (10MB per file, 25MB per message) with multimodal handoff
|
|
14
15
|
- **Persistent Memory** — wiki-based knowledge base stored at `~/.io/wiki/`
|
|
15
16
|
- **Squad System** — persistent project teams with **named specialist agents** themed from pop culture universes (dynamically selected at squad creation)
|
|
16
17
|
- **Skills** — modular skill system; install from git repos or the [skills.sh](https://skills.sh) registry
|
package/dist/api/server.js
CHANGED
|
@@ -22,6 +22,7 @@ import { saveMessage, getConversation, listConversations, searchConversations, d
|
|
|
22
22
|
import { getTokenUsageSummary, getTokenUsageBySquad, getTokenUsageByAgent, getDailyTokenUsage, } from "../store/token-usage.js";
|
|
23
23
|
import { DEFAULT_MODEL_PRICING } from "../copilot/token-tracker.js";
|
|
24
24
|
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { validateMessageAttachments } from "../chat/attachments.js";
|
|
25
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
27
|
const __dirname = dirname(__filename);
|
|
27
28
|
const sseClients = [];
|
|
@@ -33,7 +34,7 @@ function broadcast(event, data) {
|
|
|
33
34
|
}
|
|
34
35
|
export async function startApiServer(config) {
|
|
35
36
|
const app = express();
|
|
36
|
-
app.use(express.json());
|
|
37
|
+
app.use(express.json({ limit: "30mb" }));
|
|
37
38
|
// Serve static web frontend
|
|
38
39
|
const webDistPath = resolve(__dirname, "..", "..", "web-dist");
|
|
39
40
|
app.use(express.static(webDistPath));
|
|
@@ -64,14 +65,20 @@ export async function startApiServer(config) {
|
|
|
64
65
|
});
|
|
65
66
|
// --- Chat (SSE-streamed response) ---
|
|
66
67
|
app.post("/api/message", async (req, res) => {
|
|
67
|
-
const { prompt, conversationId: clientConvId } = req.body;
|
|
68
|
+
const { prompt, conversationId: clientConvId, attachments: rawAttachments } = req.body;
|
|
68
69
|
if (!prompt || typeof prompt !== "string") {
|
|
69
70
|
res.status(400).json({ error: "prompt is required" });
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
73
|
+
const validation = validateMessageAttachments(rawAttachments);
|
|
74
|
+
if (!validation.ok) {
|
|
75
|
+
res.status(400).json({ error: validation.error });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const attachments = validation.attachments;
|
|
72
79
|
const conversationId = (typeof clientConvId === "string" && clientConvId) ? clientConvId : randomUUID();
|
|
73
80
|
// Persist the user message
|
|
74
|
-
saveMessage(conversationId, "user", prompt, "web");
|
|
81
|
+
saveMessage(conversationId, "user", prompt, "web", attachments);
|
|
75
82
|
// Switch to SSE streaming to avoid Cloudflare 524 timeouts
|
|
76
83
|
res.setHeader("Content-Type", "text/event-stream");
|
|
77
84
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -99,7 +106,7 @@ export async function startApiServer(config) {
|
|
|
99
106
|
// Also broadcast to other SSE listeners (e.g. ChatOverlay)
|
|
100
107
|
broadcast("message_delta", { content, done: false });
|
|
101
108
|
}
|
|
102
|
-
});
|
|
109
|
+
}, attachments);
|
|
103
110
|
}
|
|
104
111
|
catch (err) {
|
|
105
112
|
if (!closed) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
2
|
+
export const MAX_TOTAL_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
3
|
+
const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
4
|
+
function isValidBase64(value) {
|
|
5
|
+
if (!value || value.length % 4 !== 0)
|
|
6
|
+
return false;
|
|
7
|
+
return BASE64_RE.test(value);
|
|
8
|
+
}
|
|
9
|
+
function isMessageAttachment(value) {
|
|
10
|
+
if (!value || typeof value !== "object")
|
|
11
|
+
return false;
|
|
12
|
+
const candidate = value;
|
|
13
|
+
return (typeof candidate.name === "string" &&
|
|
14
|
+
candidate.name.trim().length > 0 &&
|
|
15
|
+
typeof candidate.mimeType === "string" &&
|
|
16
|
+
candidate.mimeType.trim().length > 0 &&
|
|
17
|
+
typeof candidate.size === "number" &&
|
|
18
|
+
Number.isFinite(candidate.size) &&
|
|
19
|
+
candidate.size >= 0 &&
|
|
20
|
+
typeof candidate.content === "string");
|
|
21
|
+
}
|
|
22
|
+
export function validateMessageAttachments(input) {
|
|
23
|
+
if (input === undefined || input === null) {
|
|
24
|
+
return { ok: true, attachments: [] };
|
|
25
|
+
}
|
|
26
|
+
if (!Array.isArray(input)) {
|
|
27
|
+
return { ok: false, error: "attachments must be an array" };
|
|
28
|
+
}
|
|
29
|
+
let totalSize = 0;
|
|
30
|
+
const attachments = [];
|
|
31
|
+
for (const raw of input) {
|
|
32
|
+
if (!isMessageAttachment(raw)) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: "each attachment must include name, mimeType, size, and content",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (raw.size > MAX_ATTACHMENT_BYTES) {
|
|
39
|
+
return { ok: false, error: "each attachment must be 10MB or smaller" };
|
|
40
|
+
}
|
|
41
|
+
if (!isValidBase64(raw.content)) {
|
|
42
|
+
return { ok: false, error: `attachment "${raw.name}" has invalid base64 content` };
|
|
43
|
+
}
|
|
44
|
+
const trimmedName = raw.name.trim();
|
|
45
|
+
const trimmedMimeType = raw.mimeType.trim();
|
|
46
|
+
if (!trimmedName || !trimmedMimeType) {
|
|
47
|
+
return { ok: false, error: "attachment name and mimeType are required" };
|
|
48
|
+
}
|
|
49
|
+
totalSize += raw.size;
|
|
50
|
+
attachments.push({
|
|
51
|
+
name: trimmedName,
|
|
52
|
+
mimeType: trimmedMimeType,
|
|
53
|
+
size: raw.size,
|
|
54
|
+
content: raw.content,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (totalSize > MAX_TOTAL_ATTACHMENT_BYTES) {
|
|
58
|
+
return { ok: false, error: "total attachment size must be 25MB or smaller" };
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, attachments };
|
|
61
|
+
}
|
|
62
|
+
export function toCopilotBlobAttachments(attachments) {
|
|
63
|
+
return attachments.map((attachment) => ({
|
|
64
|
+
type: "blob",
|
|
65
|
+
data: attachment.content,
|
|
66
|
+
mimeType: attachment.mimeType,
|
|
67
|
+
displayName: attachment.name,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
export function buildAttachmentSummary(attachments) {
|
|
71
|
+
if (attachments.length === 0)
|
|
72
|
+
return "";
|
|
73
|
+
const lines = attachments.map((attachment) => `- ${attachment.name} (${attachment.mimeType}, ${Math.round(attachment.size / 1024)}KB)`);
|
|
74
|
+
return `\n\n[Attachments]\n${lines.join("\n")}`;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=attachments.js.map
|
package/dist/copilot/agents.js
CHANGED
|
@@ -12,6 +12,7 @@ import { PATHS } from "../paths.js";
|
|
|
12
12
|
import { createSquadTools } from "./squad-tools.js";
|
|
13
13
|
import { loadSkillDirectories } from "./skills.js";
|
|
14
14
|
import { getMcpServersForSession } from "../mcp/registry.js";
|
|
15
|
+
import { buildAttachmentSummary, toCopilotBlobAttachments } from "../chat/attachments.js";
|
|
15
16
|
import { existsSync, mkdirSync } from "node:fs";
|
|
16
17
|
import { join } from "node:path";
|
|
17
18
|
import { exec } from "node:child_process";
|
|
@@ -77,7 +78,7 @@ export async function stopTask(taskId) {
|
|
|
77
78
|
updateAgentStatus(task.agent_id, "idle");
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
export async function delegateTask(squadId, task, instanceId) {
|
|
81
|
+
export async function delegateTask(squadId, task, instanceId, attachments = []) {
|
|
81
82
|
const lead = getLeadForSquad(squadId);
|
|
82
83
|
if (!lead) {
|
|
83
84
|
throw new Error("Squad has no team lead. Add a lead agent first.");
|
|
@@ -102,6 +103,29 @@ export async function delegateTask(squadId, task, instanceId) {
|
|
|
102
103
|
const agentRoster = agents
|
|
103
104
|
.map((a) => `- ${a.character_name} (${a.role_title})${a.is_lead ? " [LEAD]" : ""}${a.is_qa ? " [QA]" : ""}${a.is_test ? " [TEST]" : ""}`)
|
|
104
105
|
.join("\n");
|
|
106
|
+
// Load squad wiki pages as immutable knowledge context
|
|
107
|
+
const { listPages, readPage } = await import("../wiki/fs.js");
|
|
108
|
+
const wikiPrefix = `squads/${squadSlug}`;
|
|
109
|
+
let wikiKnowledge = "";
|
|
110
|
+
try {
|
|
111
|
+
const pages = await listPages(wikiPrefix);
|
|
112
|
+
const pageContents = [];
|
|
113
|
+
for (const page of pages.slice(0, 20)) { // Cap at 20 pages to avoid token overload
|
|
114
|
+
try {
|
|
115
|
+
const content = await readPage(`${wikiPrefix}/${page}`);
|
|
116
|
+
pageContents.push(`### ${page}\n${content}`);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Skip unreadable pages
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (pageContents.length > 0) {
|
|
123
|
+
wikiKnowledge = `\n## Squad Knowledge Base\nThe following wiki pages contain important decisions, conventions, and context for this squad. Follow these as authoritative guidance:\n\n${pageContents.join("\n\n---\n\n")}\n`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Wiki not available — proceed without
|
|
128
|
+
}
|
|
105
129
|
const systemMessage = `# Squad Team Lead: ${lead.character_name}
|
|
106
130
|
|
|
107
131
|
You are ${lead.character_name}, the team lead for this squad. Your role is STRICTLY coordination — you do NOT write code, tests, or implementation of any kind.
|
|
@@ -129,7 +153,9 @@ ${agentRoster}
|
|
|
129
153
|
- Use \`--comment\` with "LGTM" for approvals (not \`--approve\`)
|
|
130
154
|
- Always use the gh CLI for GitHub interactions
|
|
131
155
|
- Merge criteria: all veto-capable members have posted approving comments + CI passes + no conflicts
|
|
132
|
-
|
|
156
|
+
- When work is complete, ALWAYS notify the user via feed_post with a summary of what was done
|
|
157
|
+
- Consult the squad wiki (wiki_read, wiki_search) for additional context beyond what's provided below
|
|
158
|
+
${wikiKnowledge}
|
|
133
159
|
${lead.persona ? `## Personality:\n${lead.persona}` : ""}
|
|
134
160
|
`;
|
|
135
161
|
let result;
|
|
@@ -169,6 +195,11 @@ ${lead.persona ? `## Personality:\n${lead.persona}` : ""}
|
|
|
169
195
|
agent: lead.character_name,
|
|
170
196
|
role: lead.role_title,
|
|
171
197
|
task,
|
|
198
|
+
attachments: attachments.map((attachment) => ({
|
|
199
|
+
name: attachment.name,
|
|
200
|
+
mimeType: attachment.mimeType,
|
|
201
|
+
size: attachment.size,
|
|
202
|
+
})),
|
|
172
203
|
});
|
|
173
204
|
// Capture streaming message deltas and broadcast via SSE
|
|
174
205
|
let accumulatedMessage = "";
|
|
@@ -186,7 +217,10 @@ ${lead.persona ? `## Personality:\n${lead.persona}` : ""}
|
|
|
186
217
|
}
|
|
187
218
|
});
|
|
188
219
|
try {
|
|
189
|
-
const response = await session.sendAndWait({
|
|
220
|
+
const response = await session.sendAndWait({
|
|
221
|
+
prompt: `Task delegated to you:\n\n${task}${buildAttachmentSummary(attachments)}`,
|
|
222
|
+
attachments: toCopilotBlobAttachments(attachments),
|
|
223
|
+
}, 600_000);
|
|
190
224
|
result = response?.data?.content ?? "Task completed (no response content).";
|
|
191
225
|
// Record the final message event if we have meaningful content
|
|
192
226
|
if (accumulatedMessage.trim()) {
|
|
@@ -4,6 +4,7 @@ import { getLeadForSquad, getAgentsForSquad, getSquad } from "../store/squads.js
|
|
|
4
4
|
import { selectModel } from "./model-router.js";
|
|
5
5
|
import { postFeedItem } from "../store/feed.js";
|
|
6
6
|
import { attachTokenTracker } from "./token-tracker.js";
|
|
7
|
+
import { buildAttachmentSummary, toCopilotBlobAttachments } from "../chat/attachments.js";
|
|
7
8
|
function buildFacilitatorPrompt(lead, agents, task) {
|
|
8
9
|
const roster = agents
|
|
9
10
|
.filter((a) => !a.is_lead)
|
|
@@ -76,7 +77,7 @@ Keep your response focused and concise — this is a planning meeting, not imple
|
|
|
76
77
|
${agent.persona ? `\n## Your Style:\n${agent.persona}` : ""}
|
|
77
78
|
`;
|
|
78
79
|
}
|
|
79
|
-
export async function planningMeeting(squadId, task) {
|
|
80
|
+
export async function planningMeeting(squadId, task, attachments = []) {
|
|
80
81
|
const lead = getLeadForSquad(squadId);
|
|
81
82
|
if (!lead) {
|
|
82
83
|
throw new Error("Squad has no team lead. Add a lead agent first.");
|
|
@@ -99,7 +100,10 @@ export async function planningMeeting(squadId, task) {
|
|
|
99
100
|
});
|
|
100
101
|
const flushTokens = attachTokenTracker(session, { squadId, agentId: agent.id });
|
|
101
102
|
try {
|
|
102
|
-
const response = await session.sendAndWait({
|
|
103
|
+
const response = await session.sendAndWait({
|
|
104
|
+
prompt: `Please provide your planning input for this task.${buildAttachmentSummary(attachments)}`,
|
|
105
|
+
attachments: toCopilotBlobAttachments(attachments),
|
|
106
|
+
}, 60_000);
|
|
103
107
|
return {
|
|
104
108
|
agent: agent.character_name,
|
|
105
109
|
role: agent.role_title,
|
|
@@ -134,7 +138,10 @@ export async function planningMeeting(squadId, task) {
|
|
|
134
138
|
let plan;
|
|
135
139
|
try {
|
|
136
140
|
const prompt = `Here is the input gathered from your team:\n\n${inputsSummary}\n\nNow synthesize this into a clear, structured action plan.`;
|
|
137
|
-
const response = await facilitatorSession.sendAndWait({
|
|
141
|
+
const response = await facilitatorSession.sendAndWait({
|
|
142
|
+
prompt,
|
|
143
|
+
attachments: toCopilotBlobAttachments(attachments),
|
|
144
|
+
}, 120_000);
|
|
138
145
|
plan = response?.data?.content ?? "Planning meeting completed but no plan was produced.";
|
|
139
146
|
}
|
|
140
147
|
finally {
|
|
@@ -146,8 +153,8 @@ export async function planningMeeting(squadId, task) {
|
|
|
146
153
|
participants: [lead.character_name, ...inputs.map((i) => i.agent)],
|
|
147
154
|
};
|
|
148
155
|
}
|
|
149
|
-
export async function squadMeeting(squadId, task, executeAfter) {
|
|
150
|
-
const result = await planningMeeting(squadId, task);
|
|
156
|
+
export async function squadMeeting(squadId, task, executeAfter, attachments = []) {
|
|
157
|
+
const result = await planningMeeting(squadId, task, attachments);
|
|
151
158
|
const summary = `## Planning Meeting Complete\n\n**Participants:** ${result.participants.join(", ")}\n\n${result.plan}`;
|
|
152
159
|
if (!executeAfter) {
|
|
153
160
|
// Post to feed and wait for user to trigger execution
|
|
@@ -159,7 +166,7 @@ export async function squadMeeting(squadId, task, executeAfter) {
|
|
|
159
166
|
// Execute: delegate with the plan as additional context
|
|
160
167
|
const { delegateTask } = await import("./agents.js");
|
|
161
168
|
const enrichedTask = `${task}\n\n---\n## Approved Plan (from team meeting)\n${result.plan}`;
|
|
162
|
-
const execResult = await delegateTask(squadId, enrichedTask);
|
|
169
|
+
const execResult = await delegateTask(squadId, enrichedTask, undefined, attachments);
|
|
163
170
|
return `Meeting held, then executed.\n\n${summary}\n\n---\n## Execution Result\n${execResult}`;
|
|
164
171
|
}
|
|
165
172
|
//# sourceMappingURL=ceremonies.js.map
|
|
@@ -7,9 +7,11 @@ import { loadSkillDirectories } from "./skills.js";
|
|
|
7
7
|
import { getMcpServersForSession } from "../mcp/registry.js";
|
|
8
8
|
import { resetClient } from "./client.js";
|
|
9
9
|
import { addAuditEntry } from "../store/audit-log.js";
|
|
10
|
+
import { buildAttachmentSummary, toCopilotBlobAttachments, } from "../chat/attachments.js";
|
|
10
11
|
let orchestratorSession;
|
|
11
12
|
let sessionCreatePromise;
|
|
12
13
|
let healthCheckInterval;
|
|
14
|
+
let activeMessageAttachments = [];
|
|
13
15
|
const messageQueue = [];
|
|
14
16
|
let processing = false;
|
|
15
17
|
export async function initOrchestrator(client, opts) {
|
|
@@ -82,9 +84,17 @@ function startHealthCheck(client, opts) {
|
|
|
82
84
|
}, 30_000);
|
|
83
85
|
healthCheckInterval.unref();
|
|
84
86
|
}
|
|
85
|
-
export async function sendToOrchestrator(prompt, source, callback) {
|
|
86
|
-
addAuditEntry("message_received", `Message from ${source}: ${prompt.slice(0, 200)}`, {
|
|
87
|
-
|
|
87
|
+
export async function sendToOrchestrator(prompt, source, callback, attachments = []) {
|
|
88
|
+
addAuditEntry("message_received", `Message from ${source}: ${prompt.slice(0, 200)}`, {
|
|
89
|
+
source,
|
|
90
|
+
prompt: prompt.slice(0, 1000),
|
|
91
|
+
attachments: attachments.map((attachment) => ({
|
|
92
|
+
name: attachment.name,
|
|
93
|
+
mimeType: attachment.mimeType,
|
|
94
|
+
size: attachment.size,
|
|
95
|
+
})),
|
|
96
|
+
});
|
|
97
|
+
messageQueue.push({ prompt, source, callback, attachments });
|
|
88
98
|
if (!processing)
|
|
89
99
|
processQueue();
|
|
90
100
|
}
|
|
@@ -109,7 +119,8 @@ async function executeOnSession(msg) {
|
|
|
109
119
|
msg.callback("Error: Orchestrator session not initialized.", true);
|
|
110
120
|
return;
|
|
111
121
|
}
|
|
112
|
-
|
|
122
|
+
activeMessageAttachments = msg.attachments;
|
|
123
|
+
const taggedPrompt = `[via ${msg.source}] ${msg.prompt}${buildAttachmentSummary(msg.attachments)}`;
|
|
113
124
|
let accumulated = "";
|
|
114
125
|
const unsubscribe = orchestratorSession.on("assistant.message_delta", (event) => {
|
|
115
126
|
const delta = event.data?.deltaContent ?? "";
|
|
@@ -117,11 +128,15 @@ async function executeOnSession(msg) {
|
|
|
117
128
|
msg.callback(accumulated, false);
|
|
118
129
|
});
|
|
119
130
|
try {
|
|
120
|
-
const response = await orchestratorSession.sendAndWait({
|
|
131
|
+
const response = await orchestratorSession.sendAndWait({
|
|
132
|
+
prompt: taggedPrompt,
|
|
133
|
+
attachments: toCopilotBlobAttachments(msg.attachments),
|
|
134
|
+
}, 600_000);
|
|
121
135
|
const finalContent = response?.data?.content ?? accumulated;
|
|
122
136
|
msg.callback(finalContent, true);
|
|
123
137
|
}
|
|
124
138
|
finally {
|
|
139
|
+
activeMessageAttachments = [];
|
|
125
140
|
unsubscribe();
|
|
126
141
|
}
|
|
127
142
|
}
|
|
@@ -132,4 +147,7 @@ export function feedAgentResult(taskId, agentName, result, callback) {
|
|
|
132
147
|
export function getOrchestratorSession() {
|
|
133
148
|
return orchestratorSession;
|
|
134
149
|
}
|
|
150
|
+
export function getActiveMessageAttachments() {
|
|
151
|
+
return activeMessageAttachments;
|
|
152
|
+
}
|
|
135
153
|
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -136,11 +136,28 @@ export function createSquadTools(squadSlug, squadId, repoUrl) {
|
|
|
136
136
|
return `Error: cannot execute commands outside the project directory`;
|
|
137
137
|
}
|
|
138
138
|
try {
|
|
139
|
+
// Ensure GitHub CLI auth is available — prefer GH_TOKEN from env,
|
|
140
|
+
// fall back to extracting from gh auth if available
|
|
141
|
+
const shellEnv = { ...process.env, GH_PROMPT_DISABLED: "1" };
|
|
142
|
+
if (!shellEnv.GH_TOKEN && !shellEnv.GITHUB_TOKEN) {
|
|
143
|
+
try {
|
|
144
|
+
const { stdout: token } = await execAsync("gh auth token", {
|
|
145
|
+
timeout: 5_000,
|
|
146
|
+
env: process.env,
|
|
147
|
+
});
|
|
148
|
+
if (token.trim()) {
|
|
149
|
+
shellEnv.GH_TOKEN = token.trim();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// gh auth not available — agents won't be able to push
|
|
154
|
+
}
|
|
155
|
+
}
|
|
139
156
|
const { stdout } = await execAsync(command, {
|
|
140
157
|
cwd: workDir,
|
|
141
158
|
timeout: 120_000,
|
|
142
159
|
maxBuffer: 2 * 1024 * 1024,
|
|
143
|
-
env:
|
|
160
|
+
env: shellEnv,
|
|
144
161
|
});
|
|
145
162
|
return stdout.trim() || "(no output)";
|
|
146
163
|
}
|
|
@@ -25,6 +25,7 @@ You are IO, a personal AI assistant daemon. You run 24/7 on the user's machine,
|
|
|
25
25
|
- Always delegate code work to the relevant squad — never implement directly
|
|
26
26
|
- Always delegate to the team lead, never to a specific agent (unless explicitly named by the user)
|
|
27
27
|
- Never delegate unless explicitly asked — creating an issue ≠ request to start work
|
|
28
|
+
- When delegating, ALWAYS instruct the squad to notify the user via the inbox (feed_post) when work is complete
|
|
28
29
|
- When creating squads, research the universe dynamically — never use hardcoded character lists
|
|
29
30
|
- **Always use the gh CLI** for all GitHub interactions (repos, issues, PRs, releases, actions, etc.). Only fall back to the GitHub API or other methods if gh is unavailable or cannot accomplish the task.
|
|
30
31
|
- For complex tasks involving multiple specialists, use squad_meeting to have the team plan together before executing. Use squad_delegate for straightforward single-domain tasks.
|
|
@@ -37,9 +38,12 @@ If a project has a squad assigned to it, you (the orchestrator) must NEVER:
|
|
|
37
38
|
- Research, analyze, or investigate the project's code, issues, or state yourself
|
|
38
39
|
- Attempt any work — even preliminary analysis — before delegating
|
|
39
40
|
- "Look into" or "check on" something before passing it to the squad
|
|
41
|
+
- Wait for the squad to finish before responding to the user
|
|
40
42
|
|
|
41
43
|
When a request comes in about a squad-owned project, you IMMEDIATELY delegate to that squad's team lead with no pre-processing. The squad handles ALL work including research, analysis, planning, and execution.
|
|
42
44
|
|
|
45
|
+
After delegating, IMMEDIATELY confirm to the user that the task is in the squad's hands (e.g., "I've handed this off to [squad name]. They'll work on it and post updates to your inbox."). Do NOT block or wait for the squad to complete — they work asynchronously in the background.
|
|
46
|
+
|
|
43
47
|
The ONLY thing you are allowed to do regarding a squad-owned project (without delegating) is:
|
|
44
48
|
- Answer questions about what the squad has already done (using feed/task history)
|
|
45
49
|
- Report squad status, task progress, or past deliverables
|
package/dist/copilot/tools.js
CHANGED
|
@@ -159,8 +159,19 @@ export function createTools() {
|
|
|
159
159
|
}),
|
|
160
160
|
handler: async ({ squad_id, task, instance_id }) => {
|
|
161
161
|
const { delegateTask } = await import("./agents.js");
|
|
162
|
-
|
|
163
|
-
const
|
|
162
|
+
const { getActiveMessageAttachments } = await import("./orchestrator.js");
|
|
163
|
+
const attachments = getActiveMessageAttachments();
|
|
164
|
+
addAuditEntry("task_delegated", `Task delegated to squad ${squad_id}: ${task.slice(0, 200)}`, {
|
|
165
|
+
squad_id,
|
|
166
|
+
task: task.slice(0, 1000),
|
|
167
|
+
instance_id,
|
|
168
|
+
attachments: attachments.map((attachment) => ({
|
|
169
|
+
name: attachment.name,
|
|
170
|
+
mimeType: attachment.mimeType,
|
|
171
|
+
size: attachment.size,
|
|
172
|
+
})),
|
|
173
|
+
}, { squad_id });
|
|
174
|
+
const result = await delegateTask(squad_id, task, instance_id, attachments);
|
|
164
175
|
return result;
|
|
165
176
|
},
|
|
166
177
|
}),
|
|
@@ -175,8 +186,19 @@ export function createTools() {
|
|
|
175
186
|
}),
|
|
176
187
|
handler: async ({ squad_id, task, execute_after }) => {
|
|
177
188
|
const { squadMeeting } = await import("./ceremonies.js");
|
|
178
|
-
|
|
179
|
-
|
|
189
|
+
const { getActiveMessageAttachments } = await import("./orchestrator.js");
|
|
190
|
+
const attachments = getActiveMessageAttachments();
|
|
191
|
+
addAuditEntry("squad_meeting", `Planning meeting started for squad ${squad_id}: ${task.slice(0, 200)}`, {
|
|
192
|
+
squad_id,
|
|
193
|
+
task: task.slice(0, 1000),
|
|
194
|
+
execute_after,
|
|
195
|
+
attachments: attachments.map((attachment) => ({
|
|
196
|
+
name: attachment.name,
|
|
197
|
+
mimeType: attachment.mimeType,
|
|
198
|
+
size: attachment.size,
|
|
199
|
+
})),
|
|
200
|
+
}, { squad_id });
|
|
201
|
+
return await squadMeeting(squad_id, task, execute_after, attachments);
|
|
180
202
|
},
|
|
181
203
|
}),
|
|
182
204
|
defineTool("squad_task_status", {
|
|
@@ -336,12 +358,28 @@ export function createTools() {
|
|
|
336
358
|
const { promisify } = await import("node:util");
|
|
337
359
|
const { homedir } = await import("node:os");
|
|
338
360
|
const execAsync = promisify(exec);
|
|
361
|
+
// Ensure GitHub CLI auth is available in the shell environment
|
|
362
|
+
const shellEnv = { ...process.env, GH_PROMPT_DISABLED: "1" };
|
|
363
|
+
if (!shellEnv.GH_TOKEN && !shellEnv.GITHUB_TOKEN) {
|
|
364
|
+
try {
|
|
365
|
+
const { stdout: token } = await execAsync("gh auth token", {
|
|
366
|
+
timeout: 5_000,
|
|
367
|
+
env: process.env,
|
|
368
|
+
});
|
|
369
|
+
if (token.trim()) {
|
|
370
|
+
shellEnv.GH_TOKEN = token.trim();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// gh auth not available
|
|
375
|
+
}
|
|
376
|
+
}
|
|
339
377
|
try {
|
|
340
378
|
const { stdout } = await execAsync(command, {
|
|
341
379
|
cwd: cwd ?? homedir(),
|
|
342
380
|
timeout: 60_000,
|
|
343
381
|
maxBuffer: 1024 * 1024,
|
|
344
|
-
env:
|
|
382
|
+
env: shellEnv,
|
|
345
383
|
});
|
|
346
384
|
const output = stdout.trim() || "(no output)";
|
|
347
385
|
addAuditEntry("shell_command", `Command: ${command.slice(0, 200)}`, { command, cwd, output: output.slice(0, 500), exit_code: 0 });
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDb } from "./db.js";
|
|
3
|
+
function parseAttachments(value) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return [];
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(value);
|
|
8
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
3
14
|
function toMessage(row) {
|
|
4
15
|
return {
|
|
5
16
|
id: row.id,
|
|
@@ -7,6 +18,7 @@ function toMessage(row) {
|
|
|
7
18
|
role: row.role,
|
|
8
19
|
content: row.content,
|
|
9
20
|
source: row.source,
|
|
21
|
+
attachments: parseAttachments(row.attachments),
|
|
10
22
|
createdAt: row.created_at,
|
|
11
23
|
};
|
|
12
24
|
}
|
|
@@ -19,10 +31,10 @@ function toSummary(row) {
|
|
|
19
31
|
updatedAt: row.updated_at,
|
|
20
32
|
};
|
|
21
33
|
}
|
|
22
|
-
export function saveMessage(conversationId, role, content, source) {
|
|
34
|
+
export function saveMessage(conversationId, role, content, source, attachments = []) {
|
|
23
35
|
const db = getDb();
|
|
24
36
|
const id = randomUUID();
|
|
25
|
-
db.prepare("INSERT INTO conversation_messages (id, conversation_id, role, content, source) VALUES (?, ?, ?, ?, ?)").run(id, conversationId, role, content, source);
|
|
37
|
+
db.prepare("INSERT INTO conversation_messages (id, conversation_id, role, content, source, attachments) VALUES (?, ?, ?, ?, ?, ?)").run(id, conversationId, role, content, source, JSON.stringify(attachments));
|
|
26
38
|
const row = db
|
|
27
39
|
.prepare("SELECT * FROM conversation_messages WHERE id = ?")
|
|
28
40
|
.get(id);
|
package/dist/store/db.js
CHANGED
|
@@ -239,6 +239,13 @@ function runMigrations(db) {
|
|
|
239
239
|
`);
|
|
240
240
|
setSchemaVersion(db, 7);
|
|
241
241
|
}
|
|
242
|
+
if (version < 8) {
|
|
243
|
+
db.exec(`
|
|
244
|
+
ALTER TABLE conversation_messages
|
|
245
|
+
ADD COLUMN attachments TEXT NOT NULL DEFAULT '[]';
|
|
246
|
+
`);
|
|
247
|
+
setSchemaVersion(db, 8);
|
|
248
|
+
}
|
|
242
249
|
}
|
|
243
250
|
function getSchemaVersion(db) {
|
|
244
251
|
const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{l as W,o as X,s as Z,u as a,k as d,h as o,n as P,D as A,a as V,m as S,z as l,T as f,N as T,F as h,w,O as F,j as b,v as n,g as _,q as ee}from"./index-
|
|
1
|
+
import{l as W,o as X,s as Z,u as a,k as d,h as o,n as P,D as A,a as V,m as S,z as l,T as f,N as T,F as h,w,O as F,j as b,v as n,g as _,q as ee}from"./index-DI8QgDXv.js";import{b as L}from"./api-2XP2yG1A.js";/**
|
|
2
2
|
* @license lucide-vue-next v0.474.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{l as U,o as R,G as W,Q as B,s as Y,u as a,k as l,h as o,D as t,j as y,F as T,w as A,z as v,n as $,T as G,O as Q,i as h,S as J,d as Z,q as k,v as x,g as I,f as ee,p as E}from"./index-DI8QgDXv.js";import{F as z}from"./file-text-Dn2yDZCL.js";import{X as te}from"./x-Cw7ZjfeL.js";/**
|
|
2
|
+
* @license lucide-vue-next v0.474.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const F=U("ImageIcon",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]]);/**
|
|
7
|
+
* @license lucide-vue-next v0.474.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const se=U("PaperclipIcon",[["path",{d:"M13.234 20.252 21 12.3",key:"1cbrk9"}],["path",{d:"m16 6-8.414 8.586a2 2 0 0 0 0 2.828 2 2 0 0 0 2.828 0l8.414-8.586a4 4 0 0 0 0-5.656 4 4 0 0 0-5.656 0l-8.415 8.585a6 6 0 1 0 8.486 8.486",key:"1pkts6"}]]),L=10*1024*1024,P=25*1024*1024;function _(n){return n<1024?`${n} B`:n<1024*1024?`${(n/1024).toFixed(1)} KB`:`${(n/(1024*1024)).toFixed(1)} MB`}function S(n){return n.mimeType.startsWith("image/")}function ae(n){return`data:${n.mimeType};base64,${n.content}`}async function ne(n){const i=await new Promise((d,g)=>{const m=new FileReader;m.onload=()=>d(String(m.result??"")),m.onerror=()=>g(new Error("Unable to read file")),m.readAsDataURL(n)}),c=i.indexOf(",");if(c===-1)throw new Error(`Unable to parse file content for ${n.name}`);return{name:n.name,mimeType:n.type||"application/octet-stream",size:n.size,content:i.slice(c+1)}}function oe(n){for(const c of n)if(c.size>L)return{ok:!1,error:`"${c.name}" exceeds the 10MB per-file limit.`};return n.reduce((c,d)=>c+d.size,0)>P?{ok:!1,error:"Attachments exceed the 25MB per-message limit."}:{ok:!0}}const re={class:"flex flex-col h-full"},le={key:0,class:"flex items-center justify-center h-full"},ce={key:0,class:"mb-2 space-y-2"},ie=["src","alt"],ue={class:"flex items-center gap-2 text-xs"},de={class:"truncate"},me={class:"opacity-70"},pe={key:2,class:"text-muted-foreground"},fe={key:3,class:"inline-block w-2 h-4 bg-current animate-pulse ml-1"},ve={key:0,class:"mb-2 space-y-2"},he={class:"flex flex-wrap gap-2"},ge={class:"max-w-[170px] truncate"},ye={class:"opacity-70"},xe=["onClick"],_e={class:"text-xs text-muted-foreground"},be={key:1,class:"text-xs text-destructive mb-2"},ke={class:"flex gap-2 items-end"},we=["disabled"],Te=["disabled"],Ce=R({__name:"ChatView",setup(n){const i=W(),c=x(""),d=x(""),g=x(),m=x(),p=x([]),b=x(!1),j=I(()=>p.value.reduce((e,r)=>e+r.size,0)),D=I(()=>!i.isStreaming&&(c.value.trim().length>0||p.value.length>0));async function C(e){if(!e||e.length===0)return;d.value="";const r=[];try{for(const f of Array.from(e))r.push(await ne(f))}catch(f){d.value=(f==null?void 0:f.message)??"Unable to read one or more files.";return}const s=[...p.value,...r],u=oe(s);if(!u.ok){d.value=u.error;return}p.value=s,m.value&&(m.value.value="")}function N(e){p.value.splice(e,1),d.value=""}function O(){var e;(e=m.value)==null||e.click()}function V(e){const r=e.target;C((r==null?void 0:r.files)??null)}async function M(){if(!D.value)return;const e=c.value.trim(),r=[...p.value],s=e||"Please review the attached file(s).";c.value="",p.value=[],d.value="",await i.sendMessage(s,r)}function w(){g.value&&(g.value.scrollTop=g.value.scrollHeight)}function K(e){e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),M())}function q(e){e.preventDefault(),b.value=!0}function H(e){e.preventDefault(),b.value=!1}async function X(e){var r;e.preventDefault(),b.value=!1,await C(((r=e.dataTransfer)==null?void 0:r.files)??null)}return B(()=>i.messages.map(e=>e.content),async()=>{await E(),w()},{deep:!0}),B(()=>i.messages.length,async()=>{await E(),w()}),Y(()=>w()),(e,r)=>(a(),l("div",re,[o("div",{ref_key:"messagesContainer",ref:g,class:"flex-1 overflow-y-auto p-4 space-y-4"},[t(i).messages.length===0?(a(),l("div",le,[...r[1]||(r[1]=[o("div",{class:"text-center text-muted-foreground"},[o("div",{class:"text-4xl mb-3"},"🤖"),o("p",{class:"text-lg font-medium"},"Welcome to IO"),o("p",{class:"text-sm mt-1"},"Send a message to get started.")],-1)])])):y("",!0),(a(!0),l(T,null,A(t(i).messages,s=>(a(),l("div",{key:s.id,class:k(["flex",s.role==="user"?"justify-end":"justify-start"])},[o("div",{class:k(["max-w-[75%] rounded-lg px-4 py-2 text-sm",s.role==="user"?"bg-primary text-primary-foreground":"bg-muted text-foreground"])},[s.attachments.length>0?(a(),l("div",ce,[(a(!0),l(T,null,A(s.attachments,(u,f)=>(a(),l("div",{key:`${s.id}-${f}`,class:"rounded border border-border/50 p-2 bg-background/70 text-foreground"},[t(S)(u)?(a(),l("img",{key:0,src:t(ae)(u),alt:u.name,class:"max-h-44 rounded mb-1 object-contain"},null,8,ie)):y("",!0),o("div",ue,[t(S)(u)?(a(),h(t(F),{key:0,class:"w-3.5 h-3.5"})):(a(),h(t(z),{key:1,class:"w-3.5 h-3.5"})),o("span",de,v(u.name),1),o("span",me,v(t(_)(u.size)),1)])]))),128))])):y("",!0),s.content?(a(),h(ee,{key:1,content:s.content,class:k(s.role==="user"?"prose-invert":"")},null,8,["content","class"])):(a(),l("span",pe,"...")),s.streaming?(a(),l("div",fe)):y("",!0)],2)],2))),128))],512),o("div",{class:k(["border-t border-border p-4",b.value?"bg-accent/40":""]),onDragover:q,onDragleave:H,onDrop:X},[o("input",{ref_key:"fileInput",ref:m,type:"file",multiple:"",class:"hidden",onChange:V},null,544),p.value.length>0?(a(),l("div",ve,[o("div",he,[(a(!0),l(T,null,A(p.value,(s,u)=>(a(),l("div",{key:`${s.name}-${u}`,class:"flex items-center gap-2 rounded border border-border px-2 py-1 text-xs bg-muted"},[t(S)(s)?(a(),h(t(F),{key:0,class:"w-3.5 h-3.5"})):(a(),h(t(z),{key:1,class:"w-3.5 h-3.5"})),o("span",ge,v(s.name),1),o("span",ye,v(t(_)(s.size)),1),o("button",{class:"hover:text-destructive",onClick:f=>N(u)},[$(t(te),{class:"w-3.5 h-3.5"})],8,xe)]))),128))]),o("p",_e,v(t(_)(j.value))+" attached · Max per file "+v(t(_)(t(L)))+" · Max total "+v(t(_)(t(P))),1)])):y("",!0),d.value?(a(),l("p",be,v(d.value),1)):y("",!0),o("div",ke,[o("button",{class:"rounded-md border border-input p-2 hover:bg-accent disabled:opacity-50",disabled:t(i).isStreaming,onClick:O,title:"Attach files"},[$(t(se),{class:"w-4 h-4"})],8,we),G(o("textarea",{"onUpdate:modelValue":r[0]||(r[0]=s=>c.value=s),onKeydown:K,placeholder:"Send a message...",rows:"1",class:"flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring min-h-[40px] max-h-[120px]"},null,544),[[Q,c.value]]),o("button",{onClick:M,disabled:!D.value,class:"rounded-md bg-primary text-primary-foreground p-2 hover:bg-primary/90 disabled:opacity-50 transition-colors"},[t(i).isStreaming?(a(),h(t(Z),{key:1,class:"w-4 h-4"})):(a(),h(t(J),{key:0,class:"w-4 h-4"}))],8,Te)])],34)]))}});export{Ce as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{l as M,o as D,s as N,u as o,k as n,h as a,F as y,w as b,n as c,D as i,I as V,v as d,g as z,q as f,z as v,r as B,W as k,j as q,f as j}from"./index-
|
|
1
|
+
import{l as M,o as D,s as N,u as o,k as n,h as a,F as y,w as b,n as c,D as i,I as V,v as d,g as z,q as f,z as v,r as B,W as k,j as q,f as j}from"./index-DI8QgDXv.js";import{b as C,c as P,a as T}from"./api-2XP2yG1A.js";import{g as W}from"./squad-colors-B8B_Y-lz.js";import{T as A}from"./trash-2-C86P23n5.js";/**
|
|
2
2
|
* @license lucide-vue-next v0.474.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as U,Q as q,s as z,u as r,k as n,h as t,n as d,D as c,H as V,T,O as D,M as P,j as x,F as $,w as j,q as p,z as g,v as l,g as Q,i as E,f as G}from"./index-
|
|
1
|
+
import{o as U,Q as q,s as z,u as r,k as n,h as t,n as d,D as c,H as V,T,O as D,M as P,j as x,F as $,w as j,q as p,z as g,v as l,g as Q,i as E,f as G}from"./index-DI8QgDXv.js";import{b as A,a as O}from"./api-2XP2yG1A.js";import{S as R}from"./search-BnsxENnH.js";import{A as I}from"./arrow-left-BsbWSKAE.js";import{T as J}from"./trash-2-C86P23n5.js";const K={class:"flex h-full"},W={class:"p-3 border-b border-border space-y-2"},X={class:"flex items-center gap-2"},Y={class:"relative"},Z={class:"flex gap-2"},ee={class:"flex-1"},te={class:"flex-1"},se={class:"flex-1 overflow-y-auto"},oe={key:0,class:"p-4 text-xs text-muted-foreground"},re={key:1,class:"flex flex-col items-center justify-center h-full p-6 text-center text-muted-foreground"},ne=["onClick"],ae={class:"flex-1 min-w-0"},le={class:"text-xs text-foreground line-clamp-2"},ie={class:"flex items-center gap-2 mt-1"},ue={class:"text-xs text-muted-foreground"},de={class:"text-xs text-muted-foreground"},ce=["onClick"],fe={key:2,class:"p-3 text-center"},ve={key:0,class:"flex-1 flex flex-col"},me={class:"flex items-center gap-2 px-4 py-2 border-b border-border"},xe={class:"text-sm font-medium text-muted-foreground"},pe={class:"flex-1 overflow-y-auto p-4 space-y-4"},ge={key:0,class:"text-center text-xs text-muted-foreground py-8"},he={class:"text-xs mt-1 opacity-60"},ye={key:1,class:"hidden md:flex flex-1 items-center justify-center text-muted-foreground"},be={class:"text-center"},_e=50,Le=U({__name:"HistoryView",setup(we){const a=l([]),h=l(0),y=l(!0),f=l(""),v=l(""),m=l(""),u=l(null),b=l([]),_=l(!1),w=l(0);async function k(o=!0){y.value=!0;try{o&&(w.value=0,a.value=[]);const e=new URLSearchParams;f.value&&e.set("q",f.value),v.value&&e.set("from",v.value),m.value&&e.set("to",m.value+"T23:59:59"),e.set("limit",String(_e)),e.set("offset",String(w.value));const i=await A(`/history?${e.toString()}`);a.value=o?i.items:[...a.value,...i.items],h.value=i.total,w.value+=i.items.length}finally{y.value=!1}}async function B(o){u.value=o,_.value=!0;try{b.value=await A(`/history/${o}`)}finally{_.value=!1}}function L(){u.value=null,b.value=[]}async function F(o,e){e.stopPropagation(),confirm("Delete this conversation?")&&(await O(`/history/${o}`),a.value=a.value.filter(i=>i.id!==o),h.value=Math.max(0,h.value-1),u.value===o&&L())}function C(o){return new Date(o).toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function H(o,e=100){return o.length>e?o.slice(0,e)+"…":o}const N=Q(()=>a.value.length<h.value);let S=null;return q([f,v,m],()=>{S&&clearTimeout(S),S=setTimeout(()=>k(!0),300)}),z(()=>k(!0)),(o,e)=>{var i;return r(),n("div",K,[t("div",{class:p(["flex flex-col border-r border-border",u.value?"hidden md:flex w-80 shrink-0":"flex-1"])},[t("div",W,[t("div",X,[d(c(V),{class:"w-4 h-4 text-muted-foreground"}),e[4]||(e[4]=t("span",{class:"text-sm font-medium"},"Conversation History",-1))]),t("div",Y,[d(c(R),{class:"absolute left-2.5 top-2.5 w-3.5 h-3.5 text-muted-foreground"}),T(t("input",{"onUpdate:modelValue":e[0]||(e[0]=s=>f.value=s),placeholder:"Search conversations...",class:"w-full rounded-md border border-input bg-background pl-8 pr-3 py-2 text-xs focus:outline-none focus:ring-1 focus:ring-ring"},null,512),[[D,f.value]])]),t("div",Z,[t("div",ee,[e[5]||(e[5]=t("label",{class:"text-xs text-muted-foreground block mb-1"},"From",-1)),T(t("input",{"onUpdate:modelValue":e[1]||(e[1]=s=>v.value=s),type:"date",class:"w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-ring"},null,512),[[D,v.value]])]),t("div",te,[e[6]||(e[6]=t("label",{class:"text-xs text-muted-foreground block mb-1"},"To",-1)),T(t("input",{"onUpdate:modelValue":e[2]||(e[2]=s=>m.value=s),type:"date",class:"w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-ring"},null,512),[[D,m.value]])])])]),t("div",se,[y.value&&a.value.length===0?(r(),n("div",oe," Loading... ")):a.value.length===0?(r(),n("div",re,[d(c(P),{class:"w-10 h-10 mb-3 opacity-40"}),e[7]||(e[7]=t("p",{class:"text-sm"},"No conversations found.",-1)),e[8]||(e[8]=t("p",{class:"text-xs mt-1"},"Start chatting to build up your history.",-1))])):x("",!0),(r(!0),n($,null,j(a.value,s=>(r(),n("div",{key:s.id,class:p(["group flex items-start gap-2 px-3 py-3 border-b border-border cursor-pointer hover:bg-accent/50 transition-colors",u.value===s.id?"bg-accent":""]),onClick:M=>B(s.id)},[t("div",ae,[t("p",le,g(H(s.preview)),1),t("div",ie,[t("span",ue,g(C(s.updatedAt)),1),t("span",de,"· "+g(s.messageCount)+" msgs",1)])]),t("button",{class:"opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-all",title:"Delete",onClick:M=>F(s.id,M)},[d(c(J),{class:"w-3.5 h-3.5"})],8,ce)],10,ne))),128)),N.value?(r(),n("div",fe,[t("button",{class:"text-xs text-muted-foreground hover:text-foreground underline",onClick:e[3]||(e[3]=s=>k(!1))}," Load more ")])):x("",!0)])],2),u.value?(r(),n("div",ve,[t("div",me,[t("button",{class:"p-1.5 rounded hover:bg-accent text-muted-foreground",title:"Back",onClick:L},[d(c(I),{class:"w-4 h-4"})]),t("span",xe,g(C(((i=a.value.find(s=>s.id===u.value))==null?void 0:i.startedAt)??"")),1)]),t("div",pe,[_.value?(r(),n("div",ge," Loading... ")):x("",!0),(r(!0),n($,null,j(b.value,s=>(r(),n("div",{key:s.id,class:p(["flex",s.role==="user"?"justify-end":"justify-start"])},[t("div",{class:p(["max-w-[75%] rounded-lg px-4 py-2 text-sm",s.role==="user"?"bg-blue-600 text-white":"bg-muted text-foreground"])},[s.content?(r(),E(G,{key:0,content:s.content,class:p(s.role==="user"?"prose-invert":"")},null,8,["content","class"])):x("",!0),t("p",he,g(C(s.createdAt)),1)],2)],2))),128))])])):u.value?x("",!0):(r(),n("div",ye,[t("div",be,[d(c(V),{class:"w-12 h-12 mx-auto mb-3 opacity-50"}),e[9]||(e[9]=t("p",null,"Select a conversation to view",-1))])]))])}}});export{Le as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as b,E as v,u,k as i,h as t,n as y,W as w,T as m,O as p,z as f,j as h,D as g,v as d,_,K as S}from"./index-
|
|
1
|
+
import{o as b,E as v,u,k as i,h as t,n as y,W as w,T as m,O as p,z as f,j as h,D as g,v as d,_,K as S}from"./index-DI8QgDXv.js";const V={class:"min-h-screen flex items-center justify-center bg-background p-4"},k={class:"w-full max-w-sm space-y-8"},D={class:"text-center"},A={key:0,class:"text-sm text-destructive"},B=["disabled"],L=b({__name:"LoginView",setup(E){const o=v(),c=S(),r=d(""),n=d(""),s=d("");async function x(){s.value="";try{await o.login(r.value,n.value),c.push("/")}catch(l){s.value=l.message??"Login failed"}}return(l,e)=>(u(),i("div",V,[t("div",k,[t("div",D,[y(_,{size:56,class:"mx-auto mb-4"}),e[2]||(e[2]=t("h1",{class:"text-2xl font-bold bg-gradient-brand bg-clip-text text-transparent"}," IO ",-1)),e[3]||(e[3]=t("p",{class:"text-sm text-muted-foreground mt-1"},"Sign in to your dashboard",-1))]),t("form",{onSubmit:w(x,["prevent"]),class:"space-y-4 bg-card border border-border rounded-lg p-6"},[t("div",null,[e[4]||(e[4]=t("label",{class:"text-sm font-medium text-muted-foreground",for:"email"},"Email",-1)),m(t("input",{id:"email","onUpdate:modelValue":e[0]||(e[0]=a=>r.value=a),type:"email",required:"",class:"mt-1 w-full rounded-md border border-border bg-input px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring",placeholder:"you@example.com"},null,512),[[p,r.value]])]),t("div",null,[e[5]||(e[5]=t("label",{class:"text-sm font-medium text-muted-foreground",for:"password"},"Password",-1)),m(t("input",{id:"password","onUpdate:modelValue":e[1]||(e[1]=a=>n.value=a),type:"password",required:"",class:"mt-1 w-full rounded-md border border-border bg-input px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring",placeholder:"••••••••"},null,512),[[p,n.value]])]),s.value?(u(),i("div",A,f(s.value),1)):h("",!0),t("button",{type:"submit",disabled:g(o).loading,class:"btn-gradient w-full py-2.5"},f(g(o).loading?"Signing in...":"Sign In"),9,B)],32),e[6]||(e[6]=t("p",{class:"text-center text-xs text-muted-foreground"}," Personal AI Assistant Daemon ",-1))])]))}});export{L as default};
|