pikiloom 0.4.38 → 0.4.40
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/dashboard/dist/assets/{AgentTab-DbFzaIyZ.js → AgentTab-Cj7sekcS.js} +1 -1
- package/dashboard/dist/assets/{ConnectionModal-QNYOWHCU.js → ConnectionModal-g_6IHAWE.js} +1 -1
- package/dashboard/dist/assets/{DirBrowser-BGaKHjFT.js → DirBrowser-DVe6q12N.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-D4Gv9b8z.js → ExtensionsTab-GqxD61Pp.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-DxxoZd84.js → IMAccessTab-DIqTzZJ9.js} +1 -1
- package/dashboard/dist/assets/{Modal-Nm2e93s5.js → Modal-CIYrMwm0.js} +1 -1
- package/dashboard/dist/assets/{Modals-BwxZmU0U.js → Modals-B-Afu2rD.js} +1 -1
- package/dashboard/dist/assets/{Select-SGlII0yx.js → Select-B1UL27gw.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-BHuG5hxj.js +1 -0
- package/dashboard/dist/assets/{SystemTab-BF6lIAYM.js → SystemTab-Bv1uT5-h.js} +1 -1
- package/dashboard/dist/assets/{index-DZiAiRNt.js → index-BOEW0RIx.js} +14 -14
- package/dashboard/dist/assets/index-Bthwt6K_.css +1 -0
- package/dashboard/dist/assets/{index-BP8R_bLT.js → index-qKexKdJl.js} +3 -3
- package/dashboard/dist/assets/{shared-S0kcs5yP.js → shared-624PaUfm.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/drivers/codex.js +5 -3
- package/dist/agent/index.js +1 -1
- package/dist/agent/kernel-bridge.js +1 -0
- package/dist/agent/mcp/capabilities.js +39 -0
- package/dist/agent/stream.js +13 -1
- package/dist/bot/bot.js +4 -13
- package/dist/dashboard/routes/agents.js +8 -4
- package/package.json +1 -1
- package/packages/kernel/README.md +263 -65
- package/packages/kernel/dist/contracts/surface.d.ts +17 -0
- package/packages/kernel/dist/drivers/claude.d.ts +12 -1
- package/packages/kernel/dist/drivers/claude.js +69 -5
- package/packages/kernel/dist/drivers/codex.d.ts +28 -1
- package/packages/kernel/dist/drivers/codex.js +161 -16
- package/packages/kernel/dist/index.d.ts +1 -1
- package/packages/kernel/dist/runtime/hub.d.ts +3 -0
- package/packages/kernel/dist/runtime/hub.js +84 -10
- package/packages/kernel/dist/runtime/loom.d.ts +1 -0
- package/packages/kernel/dist/runtime/loom.js +4 -1
- package/dashboard/dist/assets/SessionPanel-DYfSlreh.js +0 -1
- package/dashboard/dist/assets/index-CtS48Jn-.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as s}from"./react-vendor-C7Sl8SE7.js";import{N as t,m as i,S as a}from"./index-
|
|
1
|
+
import{j as s}from"./react-vendor-C7Sl8SE7.js";import{N as t,m as i,S as a}from"./index-qKexKdJl.js";function o({primary:e,secondary:l,tertiary:n}){return s.jsxs("div",{className:"flex flex-col gap-2 pt-1 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between",children:[s.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e&&s.jsxs(i,{tone:"primary",onClick:e.onClick,disabled:e.disabled,children:[e.loading&&s.jsx(a,{}),e.label]}),l&&s.jsx(i,{tone:"secondary",onClick:l.onClick,disabled:l.disabled,children:l.label})]}),n&&s.jsx("div",{className:"text-xs leading-relaxed text-fg-4",children:n})]})}function c({children:e,className:l}){return s.jsx(t,{padding:"md",elevation:"flat",className:l,children:e})}export{o as A,c as S};
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<link rel="icon" type="image/png" href="/logo.png">
|
|
7
7
|
<title>Pikiloom</title>
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-qKexKdJl.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-C7Sl8SE7.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/router-DHISdpPk.js">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bthwt6K_.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -34,9 +34,11 @@ export class CodexAppServer {
|
|
|
34
34
|
return new Promise((resolve) => {
|
|
35
35
|
const timer = setTimeout(() => { this.kill(); resolve(false); }, CODEX_APPSERVER_SPAWN_TIMEOUT_MS);
|
|
36
36
|
const args = ['app-server'];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Do NOT force model_reasoning_summary — reasoning summaries stay OFF by default (respecting
|
|
38
|
+
// ~/.codex/config.toml = original behavior). A caller wanting thinking passes it via configOverrides.
|
|
39
|
+
const overrides = [...this.configOverrides];
|
|
40
|
+
if (!overrides.some(entry => /^features\.goals\s*=/.test(entry)))
|
|
41
|
+
overrides.push('features.goals=true');
|
|
40
42
|
for (const c of overrides)
|
|
41
43
|
args.push('-c', c);
|
|
42
44
|
agentLog(`[codex-rpc] spawning: codex ${args.join(' ')}`);
|
package/dist/agent/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { attachAgentImage, attachInlineImage, materializeImage, rewriteAttachmen
|
|
|
7
7
|
export { deliverArtifact, readDeliveredArtifacts, deliveredArtifactBlocks, tailDeliveredBlocks, latestDeliveredTaskId, mimeForArtifact, } from './artifacts.js';
|
|
8
8
|
export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
|
|
9
9
|
export { updateSessionMeta, promoteSessionId, recordFork, resolveCanonicalSessionId, getSessionPromotions, listPikiloomSessions, findPikiloomSession, getSessionStoredConfig, ensureManagedSession, findManagedThreadSession, stageSessionFiles, mergeManagedAndNativeSessions, managedRecordToSessionInfo, getSessions, getSessionTail, getSessionMessages, applyTurnWindow, applyTurnFilter, classifySession, deriveUserStatus, exportSession, importSession, deleteAgentSession, isProcessAlive, isRunningSessionStale, reconcileOrphanedRunningSessions, } from './session.js';
|
|
10
|
-
export { detectAgentBin, listAgents, resolveDefaultAgent, run, doStream, recoverProfileIdForModel, listModels, resolveAgentModels, getUsage, getAgentBoundModelId, setAgentBoundModelId, } from './stream.js';
|
|
10
|
+
export { detectAgentBin, listAgents, resolveDefaultAgent, run, doStream, recoverProfileIdForModel, listModels, resolveAgentModels, dropNativeShadowedByProfiles, getUsage, getAgentBoundModelId, setAgentBoundModelId, } from './stream.js';
|
|
11
11
|
export { registerDriver, getDriver, getDriverCapabilities, allDrivers, allDriverIds, hasDriver, shutdownAllDrivers, } from './driver.js';
|
|
12
12
|
export { getProjectSkillPaths, initializeProjectSkills, listSkills, getGlobalSkillsRoot, collapseSkillPrompt, } from './skills.js';
|
|
13
13
|
export { readGoal, writeGoal, clearGoal, setGoal, pauseGoal, resumeGoal, completeGoal, accountTurn, bumpContinuationCount, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, sessionGoalPath, DEFAULT_MAX_CONTINUATIONS, } from './goal.js';
|
|
@@ -135,6 +135,7 @@ export async function kernelStream(opts) {
|
|
|
135
135
|
cachedInputTokens: s.usage?.cachedInputTokens ?? null,
|
|
136
136
|
contextUsedTokens: s.usage?.contextUsedTokens ?? null,
|
|
137
137
|
contextPercent: s.usage?.contextPercent ?? null,
|
|
138
|
+
turnOutputTokens: s.usage?.turnOutputTokens ?? null,
|
|
138
139
|
toolCalls: s.toolCalls?.length ? s.toolCalls : undefined,
|
|
139
140
|
subAgents: s.subAgents?.length ? s.subAgents : undefined,
|
|
140
141
|
providerName: opts.byokProviderName ?? null,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Session tool capabilities: each co-locates a session MCP tool group (defined under
|
|
2
|
+
// ./tools/) with the prompt fragment that teaches the agent to use it. This is the local,
|
|
3
|
+
// decoupled analog of a @pikiloom/kernel Plugin (a capability = its tools + its usage
|
|
4
|
+
// prompt, as one unit). pikiloom composes its own system prompt app-side and feeds the
|
|
5
|
+
// kernel a finished string, so this module intentionally does NOT import the kernel — that
|
|
6
|
+
// is what keeps pikiloom fully decoupled from the kernel's Hub/plugin runtime.
|
|
7
|
+
const ARTIFACT_RETURN = [
|
|
8
|
+
'[Artifact Return]',
|
|
9
|
+
'To hand a file to the user — a screenshot, report, archive, generated asset, anything they asked you to "send" — call the `im_send_file` tool with the file path and a short caption. It is delivered through whatever terminal the user is on (an IM chat or the web dashboard) and stays retrievable even when they are connected remotely. Do NOT just print a local filesystem path: a remote user cannot open paths on this machine.',
|
|
10
|
+
].join('\n');
|
|
11
|
+
const ASK_USER = [
|
|
12
|
+
'[Asking the user]',
|
|
13
|
+
'The built-in `AskUserQuestion` tool is disabled here and will fail. If you would otherwise call it, call `mcp__pikiloom__im_ask_user` instead — same intent (a question plus optional choices), it blocks until the user replies via the IM/dashboard channel. Default behaviour is unchanged: infer obvious decisions yourself and only ask when you genuinely cannot proceed.',
|
|
14
|
+
].join('\n');
|
|
15
|
+
export const SESSION_TOOL_CAPABILITIES = [
|
|
16
|
+
{
|
|
17
|
+
id: 'artifact-delivery',
|
|
18
|
+
tools: ['im_send_file', 'im_list_files'],
|
|
19
|
+
promptFragment: () => ARTIFACT_RETURN,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'ask-user',
|
|
23
|
+
tools: ['im_ask_user'],
|
|
24
|
+
// Only when the HITL path is wired and the agent is claude (codex/gemini handle asks natively).
|
|
25
|
+
promptFragment: ({ agent, onInteraction }) => (onInteraction && agent === 'claude') ? ASK_USER : null,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
// Compose the session tool prompt = each capability's applicable fragment, in registration
|
|
29
|
+
// order, joined by a blank line. Byte-identical to the prior inline assembly in bot.ts.
|
|
30
|
+
export function composeSessionToolPrompt(ctx) {
|
|
31
|
+
const parts = [];
|
|
32
|
+
for (const cap of SESSION_TOOL_CAPABILITIES) {
|
|
33
|
+
const fragment = cap.promptFragment(ctx);
|
|
34
|
+
const trimmed = String(fragment || '').trim();
|
|
35
|
+
if (trimmed)
|
|
36
|
+
parts.push(trimmed);
|
|
37
|
+
}
|
|
38
|
+
return parts.join('\n\n');
|
|
39
|
+
}
|
package/dist/agent/stream.js
CHANGED
|
@@ -658,6 +658,17 @@ export function listModels(agent, opts = {}) {
|
|
|
658
658
|
function isLocalProviderBaseURL(baseURL) {
|
|
659
659
|
return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::|\/|$)/i.test(baseURL);
|
|
660
660
|
}
|
|
661
|
+
// A model surfaced as a BYOK profile must NOT also appear under the native group. Drivers seed
|
|
662
|
+
// their native list with the active model (via opts.currentModel) so it stays visible — but when
|
|
663
|
+
// a BYOK profile is active that "current model" is the profile's third-party model (e.g. a 豆包 /
|
|
664
|
+
// deepseek id), which then wrongly shows up as an "official / 官方 / Native" model. The profile row
|
|
665
|
+
// already represents it, so drop any native entry shadowed by a profile id.
|
|
666
|
+
export function dropNativeShadowedByProfiles(native, profileRows) {
|
|
667
|
+
if (!profileRows.length)
|
|
668
|
+
return native;
|
|
669
|
+
const shadowed = new Set(profileRows.map(m => m.id.trim().toLowerCase()).filter(Boolean));
|
|
670
|
+
return native.filter(m => !shadowed.has(m.id.trim().toLowerCase()));
|
|
671
|
+
}
|
|
661
672
|
export async function resolveAgentModels(agent, opts = {}) {
|
|
662
673
|
const driver = getDriver(agent);
|
|
663
674
|
let nativeResult;
|
|
@@ -667,7 +678,7 @@ export async function resolveAgentModels(agent, opts = {}) {
|
|
|
667
678
|
catch {
|
|
668
679
|
nativeResult = { agent, models: [], sources: [], note: null };
|
|
669
680
|
}
|
|
670
|
-
const
|
|
681
|
+
const nativeRaw = nativeResult.models.map(m => ({
|
|
671
682
|
id: m.id,
|
|
672
683
|
alias: m.alias,
|
|
673
684
|
group: 'native',
|
|
@@ -693,6 +704,7 @@ export async function resolveAgentModels(agent, opts = {}) {
|
|
|
693
704
|
(isLocal ? local : cloud).push(entry);
|
|
694
705
|
}
|
|
695
706
|
}
|
|
707
|
+
const native = dropNativeShadowedByProfiles(nativeRaw, [...cloud, ...local]);
|
|
696
708
|
const sources = [...nativeResult.sources];
|
|
697
709
|
if (cloud.length)
|
|
698
710
|
sources.push(`${cloud.length} cloud profile${cloud.length === 1 ? '' : 's'}`);
|
package/dist/bot/bot.js
CHANGED
|
@@ -8,6 +8,7 @@ import { getActiveProfileId, getActiveProfile, setActiveProfile } from '../model
|
|
|
8
8
|
import { querySessions, querySessionTail, updateSession, } from './session-hub.js';
|
|
9
9
|
import { getDriver, hasDriver, allDriverIds, getDriverCapabilities } from '../agent/driver.js';
|
|
10
10
|
import { resolveGuiIntegrationConfig } from '../agent/mcp/bridge.js';
|
|
11
|
+
import { composeSessionToolPrompt } from '../agent/mcp/capabilities.js';
|
|
11
12
|
import { terminateProcessTree } from '../core/process-control.js';
|
|
12
13
|
import { expandTilde } from '../core/platform.js';
|
|
13
14
|
import { VERSION } from '../core/version.js';
|
|
@@ -48,18 +49,6 @@ function appendExtraPrompt(base, extra) {
|
|
|
48
49
|
return lhs;
|
|
49
50
|
return `${lhs}\n\n${rhs}`;
|
|
50
51
|
}
|
|
51
|
-
function buildMcpDeliveryPrompt() {
|
|
52
|
-
return [
|
|
53
|
-
'[Artifact Return]',
|
|
54
|
-
'To hand a file to the user — a screenshot, report, archive, generated asset, anything they asked you to "send" — call the `im_send_file` tool with the file path and a short caption. It is delivered through whatever terminal the user is on (an IM chat or the web dashboard) and stays retrievable even when they are connected remotely. Do NOT just print a local filesystem path: a remote user cannot open paths on this machine.',
|
|
55
|
-
].join('\n');
|
|
56
|
-
}
|
|
57
|
-
function buildClaudeAskUserPrompt() {
|
|
58
|
-
return [
|
|
59
|
-
'[Asking the user]',
|
|
60
|
-
'The built-in `AskUserQuestion` tool is disabled here and will fail. If you would otherwise call it, call `mcp__pikiloom__im_ask_user` instead — same intent (a question plus optional choices), it blocks until the user replies via the IM/dashboard channel. Default behaviour is unchanged: infer obvious decisions yourself and only ask when you genuinely cannot proceed.',
|
|
61
|
-
].join('\n');
|
|
62
|
-
}
|
|
63
52
|
function buildBrowserAutomationPrompt(browserEnabled) {
|
|
64
53
|
if (!browserEnabled) {
|
|
65
54
|
return [
|
|
@@ -2022,7 +2011,9 @@ export class Bot {
|
|
|
2022
2011
|
const workflowEnabled = cs.agent === 'claude' && (extras?.workflowEnabled ?? this.claudeWorkflowEnabled);
|
|
2023
2012
|
const deliverySessionKey = ('key' in cs && typeof cs.key === 'string') ? cs.key : null;
|
|
2024
2013
|
const wrappedSendFile = this.buildArtifactSendFile(cs.agent, deliverySessionKey, cs, mcpSendFile);
|
|
2025
|
-
|
|
2014
|
+
// Session tool capabilities (im_send_file / im_ask_user) bundle their usage prompt with
|
|
2015
|
+
// the tool itself — see agent/mcp/capabilities.ts. Browser/workflow remain session policy.
|
|
2016
|
+
const mcpSystemPrompt = appendExtraPrompt(appendExtraPrompt(composeSessionToolPrompt({ agent: cs.agent, onInteraction: !!onInteraction }), buildBrowserAutomationPrompt(browserEnabled)), workflowEnabled ? buildWorkflowOptInPrompt() : '');
|
|
2026
2017
|
const effectiveSystemPrompt = isFirstTurnOfSession
|
|
2027
2018
|
? appendExtraPrompt(systemPrompt, mcpSystemPrompt)
|
|
2028
2019
|
: (mcpSystemPrompt || undefined);
|
|
@@ -137,6 +137,11 @@ async function buildAgentStatusResponse(config = loadUserConfig(), agentOptions
|
|
|
137
137
|
}
|
|
138
138
|
const runtimeSelectedModel = runtime.getRuntimeModel(agentId, config);
|
|
139
139
|
const runtimeSelectedEffort = runtime.getRuntimeEffort(agentId, config);
|
|
140
|
+
const activeProfile = getActiveProfile(agentId);
|
|
141
|
+
// runtimeSelectedModel is the model that will RUN (profile-aware): when a BYOK profile is
|
|
142
|
+
// bound it is the profile's third-party model. The NATIVE model list must not be seeded with
|
|
143
|
+
// it, else it shows under the "官方 / Native" group (it is surfaced separately via byokModels).
|
|
144
|
+
const nativeRuntimeModel = activeProfile ? '' : runtimeSelectedModel;
|
|
140
145
|
let models = [];
|
|
141
146
|
let usage = emptyUsage(agentId, 'Agent not installed.');
|
|
142
147
|
let nativeConfig = null;
|
|
@@ -149,10 +154,10 @@ async function buildAgentStatusResponse(config = loadUserConfig(), agentOptions
|
|
|
149
154
|
}
|
|
150
155
|
catch { }
|
|
151
156
|
}
|
|
152
|
-
const modelFallback =
|
|
157
|
+
const modelFallback = nativeRuntimeModel ? [{ id: nativeRuntimeModel, alias: null }] : [];
|
|
153
158
|
const cachedUsage = driver.getUsage({ agent: agentId, model: runtimeSelectedModel });
|
|
154
159
|
const [resolvedModels, resolvedUsage] = await Promise.all([
|
|
155
|
-
withTimeoutFallback(driver.listModels({ workdir, currentModel:
|
|
160
|
+
withTimeoutFallback(driver.listModels({ workdir, currentModel: nativeRuntimeModel || undefined }).then(result => dedupeModels([
|
|
156
161
|
...modelFallback,
|
|
157
162
|
...result.models,
|
|
158
163
|
])), AGENT_STATUS_MODELS_TIMEOUT_MS, modelFallback),
|
|
@@ -169,14 +174,13 @@ async function buildAgentStatusResponse(config = loadUserConfig(), agentOptions
|
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
176
|
const updateState = getAgentUpdateState(agentId);
|
|
172
|
-
const activeProfile = getActiveProfile(agentId);
|
|
173
177
|
const byokProvider = activeProfile ? getProvider(activeProfile.providerId) : null;
|
|
174
178
|
const byokProviderName = byokProvider?.name || null;
|
|
175
179
|
const byokProfileName = activeProfile
|
|
176
180
|
&& activeProfile.name.trim().toLowerCase() !== activeProfile.modelId.trim().toLowerCase()
|
|
177
181
|
? activeProfile.name
|
|
178
182
|
: null;
|
|
179
|
-
const nativeSelectedModel =
|
|
183
|
+
const nativeSelectedModel = nativeRuntimeModel || nativeConfig?.model || null;
|
|
180
184
|
const nativeSelectedEffort = runtimeSelectedEffort || nativeConfig?.effort || null;
|
|
181
185
|
const selectedModel = activeProfile?.modelId || nativeSelectedModel;
|
|
182
186
|
const selectedEffort = activeProfile?.effort || nativeSelectedEffort;
|
package/package.json
CHANGED
|
@@ -1,107 +1,305 @@
|
|
|
1
1
|
# @pikiloom/kernel
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
accumulating session snapshot + control
|
|
5
|
-
(IM, Web, tunnel). Environmental concerns
|
|
3
|
+
Turn heterogeneous coding agents (**Claude Code · Codex · Gemini · ACP/Hermes**) into one
|
|
4
|
+
**uniform, accumulating session snapshot + a small set of control verbs**, exposed over
|
|
5
|
+
**pluggable surfaces** (IM, Web, tunnel, raw terminal). Environmental concerns (storage,
|
|
6
|
+
credentials, tools, prompts) are **injected ports** with working defaults.
|
|
6
7
|
|
|
7
|
-
This is the reusable core
|
|
8
|
-
project and stand up a "pikiloom-like" backend in a few lines
|
|
8
|
+
This is the reusable core that [pikiloom](https://github.com/xiaotonng/pikiloom) itself is
|
|
9
|
+
built on. Drop it into any project and stand up a "pikiloom-like" backend in a few lines —
|
|
10
|
+
**you never parse a CLI's output or learn each agent's wire format**; you read one
|
|
11
|
+
`UniversalSnapshot` and call `prompt / stop / steer / interact`.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
```bash
|
|
14
|
+
npm i @pikiloom/kernel
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Runtime dep: `ws`. Optional: `node-pty` (only for raw-TUI passthrough). Node ≥ 20, ESM-only.
|
|
18
|
+
- TypeScript types ship in the package (`dist/**/*.d.ts`). For an LLM-oriented summary, see [`llms.txt`](./llms.txt).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Mental model
|
|
11
23
|
|
|
12
24
|
```
|
|
13
|
-
上层 (you write): IM bindings · Web UI
|
|
25
|
+
上层 (you write): IM bindings · Web UI · Plugins ── implement Surface / Plugin
|
|
14
26
|
▲ createLoom({ surfaces, plugins, ...ports })
|
|
15
|
-
@pikiloom/kernel:
|
|
16
|
-
(one import, runtime/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
@pikiloom/kernel: contracts/ Driver · Surface · LoomIO · Ports
|
|
28
|
+
(one import, runtime/ SessionRunner · Hub (multi-session) · snapshot accumulation · control verbs
|
|
29
|
+
modular inside) drivers/ Claude / Codex / Gemini / Hermes / Echo ← the agent axis (下层)
|
|
30
|
+
surfaces/ WebSurface (ws host) · CliSurface
|
|
31
|
+
protocol/ UniversalSnapshot + diff ← the wire vocabulary
|
|
32
|
+
ports/ SessionStore · ModelResolver · ToolProvider · ... (+ defaults)
|
|
33
|
+
▲ spawns the external `claude` / `codex` / `gemini` CLIs (unchanged)
|
|
34
|
+
下层 (unchanged): the agent binaries, native protocols
|
|
22
35
|
```
|
|
23
36
|
|
|
24
|
-
**IM and Web are not two systems** —
|
|
37
|
+
**IM and Web are not two systems** — both are just `Surface`s over the same `LoomIO`.
|
|
38
|
+
|
|
39
|
+
### Two rails, same driver registry
|
|
40
|
+
|
|
41
|
+
| Rail | Driver method | Output | Use for |
|
|
42
|
+
|------|---------------|--------|---------|
|
|
43
|
+
| **Structured** | `driver.run(input, ctx)` | streamed `UniversalSnapshot` (text, reasoning, tool activity, plan, usage) | IM, Web dashboards, any UI that renders structured turns |
|
|
44
|
+
| **Raw PTY** | `driver.tui(input)` | a real full-screen interactive process passed through a PTY | a local terminal app (`pikiloom code`-style); needs `node-pty` |
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
### 1. One turn through one agent — `runTurn` (the bridge primitive)
|
|
51
|
+
|
|
52
|
+
The smallest unit. No persistence, no multi-session — just run a turn and get a streamed
|
|
53
|
+
snapshot + final result. This is exactly what an existing app maps onto its own UI.
|
|
27
54
|
|
|
28
55
|
```ts
|
|
29
|
-
import {
|
|
56
|
+
import { runTurn, ClaudeDriver } from '@pikiloom/kernel';
|
|
57
|
+
|
|
58
|
+
const { result, snapshot } = await runTurn(
|
|
59
|
+
new ClaudeDriver(),
|
|
60
|
+
{ prompt: 'Summarize package.json in one line', workdir: process.cwd(), effort: 'high' },
|
|
61
|
+
{
|
|
62
|
+
onSnapshot: (s) => {
|
|
63
|
+
// fires on every event — render live:
|
|
64
|
+
// s.text accumulated assistant output
|
|
65
|
+
// s.reasoning accumulated thinking (when the model/auth exposes it)
|
|
66
|
+
// s.activity human-readable execution trail ("Read foo.ts", "Run shell: npm test")
|
|
67
|
+
// s.toolCalls structured tool calls [{ name, summary, status }]
|
|
68
|
+
// s.plan, s.usage, s.artifacts, s.interactions
|
|
69
|
+
process.stdout.write(`\r${s.activity?.split('\n').at(-1) ?? ''}`);
|
|
70
|
+
},
|
|
71
|
+
onSteer: (steer) => { /* call steer('extra prompt') mid-turn */ },
|
|
72
|
+
signal: undefined, // pass an AbortSignal to stop the turn
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
console.log(result.ok, result.text, result.sessionId);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. A full multi-session backend — `createLoom`
|
|
79
|
+
|
|
80
|
+
Adds persistence, a session hub, per-session queueing, discovery, and surfaces. Every port
|
|
81
|
+
has a default, so this runs with zero wiring.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createLoom, ClaudeDriver, CodexDriver, WebSurface } from '@pikiloom/kernel';
|
|
30
85
|
|
|
31
86
|
const loom = createLoom({
|
|
32
|
-
drivers: [new ClaudeDriver()],
|
|
33
|
-
surfaces: [new WebSurface({ port: 8787 })],
|
|
87
|
+
drivers: [new ClaudeDriver(), new CodexDriver()], // 下层 (unchanged binaries)
|
|
88
|
+
surfaces: [new WebSurface({ port: 8787 })], // 上层 (Web/tunnel; add your IM Surface here)
|
|
89
|
+
defaultAgent: 'claude',
|
|
90
|
+
// optional ports — override any one to swap storage / credentials / tools / prompts:
|
|
91
|
+
// sessionStore, modelResolver, toolProvider, systemPromptBuilder, catalog, interactionHandler
|
|
34
92
|
});
|
|
35
93
|
await loom.start();
|
|
94
|
+
|
|
95
|
+
// Drive it from anywhere via loom.io (LoomIO):
|
|
96
|
+
const { sessionKey, taskId } = await loom.io.prompt({ prompt: 'hello', agent: 'claude' });
|
|
97
|
+
const unsub = loom.io.subscribe((key, snapshot, patch, seq) => { /* render */ });
|
|
98
|
+
loom.io.steer(taskId, 'actually, do X instead');
|
|
99
|
+
loom.io.stop(sessionKey);
|
|
36
100
|
```
|
|
37
101
|
|
|
38
|
-
|
|
39
|
-
`io.prompt(...)` and render `io.subscribe(...)` snapshots back.
|
|
40
|
-
|
|
102
|
+
To add an **IM channel**, implement `Surface.start(io)`: route inbound messages to
|
|
103
|
+
`io.prompt(...)` and render `io.subscribe(...)` snapshots back out. Nothing else changes.
|
|
104
|
+
|
|
105
|
+
### 3. Raw TUI passthrough — `runTui` / `openTui`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const loom = createLoom({ drivers: [new ClaudeDriver()], defaultAgent: 'claude' });
|
|
109
|
+
await loom.runTui({ agent: 'claude', workdir: process.cwd() }); // you're now in the real Claude TUI
|
|
110
|
+
```
|
|
41
111
|
|
|
42
|
-
|
|
112
|
+
`runTui` does full stdin/stdout raw passthrough on the current terminal. `openTui` returns a
|
|
113
|
+
`PtyBridge` (`onData` / `write` / `resize` / `onExit`) so you can drive or tee it yourself.
|
|
114
|
+
Requires the optional `node-pty` dependency (`ptyAvailable()` reports availability).
|
|
43
115
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## The data you read: `UniversalSnapshot`
|
|
119
|
+
|
|
120
|
+
One driver-agnostic shape for every agent. A surface renders *this* and never touches a
|
|
121
|
+
CLI's native format. It **accumulates** across a turn (text/reasoning append; structured
|
|
122
|
+
fields replace).
|
|
47
123
|
|
|
48
124
|
```ts
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
125
|
+
interface UniversalSnapshot {
|
|
126
|
+
phase: 'idle' | 'queued' | 'streaming' | 'done';
|
|
127
|
+
taskId?: string | null;
|
|
128
|
+
sessionId?: string | null;
|
|
129
|
+
agent?: string | null;
|
|
130
|
+
model?: string | null;
|
|
131
|
+
effort?: string | null;
|
|
132
|
+
prompt?: string | null;
|
|
133
|
+
|
|
134
|
+
text?: string; // accumulated assistant output
|
|
135
|
+
reasoning?: string; // accumulated thinking (only when the model/auth streams it; see note)
|
|
136
|
+
activity?: string; // human-readable execution trail, one line per tool/subagent (kernel-derived)
|
|
137
|
+
plan?: UniversalPlan | null; // { explanation, steps:[{ text, status }] }
|
|
138
|
+
toolCalls?: UniversalToolCall[]; // structured: { id, name, summary, input?, result?, status }
|
|
139
|
+
subAgents?: UniversalSubAgent[]; // spawned sub-agents and their tools
|
|
140
|
+
usage?: UniversalUsage | null; // { inputTokens, outputTokens, cachedInputTokens, contextPercent, ... }
|
|
141
|
+
artifacts?: UniversalArtifact[]; // generated files/images { url|path, fileName, mime, kind }
|
|
142
|
+
interactions?: UniversalInteraction[]; // pending human-in-the-loop questions (answer via interact())
|
|
143
|
+
queued?: UniversalQueuedTask[]; // prompts waiting behind the running turn
|
|
144
|
+
|
|
145
|
+
error?: string | null;
|
|
146
|
+
incomplete?: boolean; // true if the turn was interrupted / errored
|
|
147
|
+
startedAt?: number;
|
|
148
|
+
updatedAt: number;
|
|
149
|
+
}
|
|
52
150
|
```
|
|
53
151
|
|
|
54
|
-
|
|
55
|
-
a `PtyBridge` (`onData`/`write`/`resize`/`onExit`) so you can drive or tee it yourself.
|
|
56
|
-
Requires the optional `node-pty` dependency.
|
|
152
|
+
Sub-shapes:
|
|
57
153
|
|
|
58
|
-
|
|
154
|
+
```ts
|
|
155
|
+
interface UniversalToolCall { id: string; name: string; summary: string; input?: string | null; result?: string | null; status: 'running' | 'done' | 'failed'; }
|
|
156
|
+
interface UniversalPlan { explanation: string | null; steps: { text: string; status: 'pending' | 'inProgress' | 'completed' }[]; }
|
|
157
|
+
interface UniversalUsage { inputTokens: number | null; outputTokens: number | null; cachedInputTokens: number | null; contextUsedTokens?: number | null; contextPercent: number | null; providerName?: string | null; }
|
|
158
|
+
interface UniversalArtifact { url?: string; path?: string; fileName: string; mime?: string; kind: 'photo' | 'document'; caption?: string; }
|
|
159
|
+
interface UniversalInteraction { promptId: string; kind: 'user-input' | 'permission' | 'confirmation'; title: string; questions: { id: string; text: string; type?: 'text' | 'select'; choices?: { label: string; value?: string }[] }[]; }
|
|
160
|
+
```
|
|
59
161
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
162
|
+
**Activity projection (kernel-owned).** Every driver emits *structured* tool calls; the
|
|
163
|
+
kernel's `SessionRunner` derives `snapshot.activity` from `toolCalls` + `subAgents` centrally
|
|
164
|
+
(`projectActivity`): one line per call with a status suffix — `summary` while running,
|
|
165
|
+
`summary done` / `summary -> detail` on success, `summary failed: detail` on error. So every
|
|
166
|
+
surface gets a readable execution trail for free, and rich UIs can still use `toolCalls`
|
|
167
|
+
directly. You implement this **nowhere** — it's a property of the snapshot.
|
|
168
|
+
|
|
169
|
+
**Reasoning note.** Plaintext thinking only appears when the agent actually streams it
|
|
170
|
+
(e.g. BYOK Anthropic API keys, Codex `item/reasoning`). Subscription/OAuth Claude withholds
|
|
171
|
+
plaintext extended-thinking (streams only an encrypted signature), so `reasoning` is empty
|
|
172
|
+
there — a platform behavior, not a kernel limitation. The Claude driver also captures
|
|
173
|
+
reasoning delivered as a complete block, not only as streamed deltas.
|
|
174
|
+
|
|
175
|
+
### Streaming on the wire — `diffSnapshot` / `applySnapshotPatch`
|
|
176
|
+
|
|
177
|
+
`diffSnapshot(prev, next)` produces a compact `SnapshotPatch` (prefix-append for `text` /
|
|
178
|
+
`reasoning`; field replacement otherwise, with `undefined → null` so field-clears survive
|
|
179
|
+
`JSON.stringify`). `applySnapshotPatch(prev, patch)` reassembles it on the client. `WebSurface`
|
|
180
|
+
uses these; reuse them for your own transport.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Control verbs (`LoomIO`)
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
io.prompt({ prompt, agent?, sessionKey?, workdir?, model?, effort?, attachments? }) // → { sessionKey, taskId }
|
|
188
|
+
io.stop(sessionKey) // interrupt the running turn (queued tasks stay & promote)
|
|
189
|
+
io.steer(taskId, prompt, attachments?) // inject a message mid-turn (drivers with capabilities.steer)
|
|
190
|
+
io.interact(promptId, action, value?) // answer a pending interaction: 'select' | 'text' | 'skip' | 'cancel'
|
|
191
|
+
io.subscribe((sessionKey, snapshot, patch, seq) => …) // live snapshots
|
|
192
|
+
io.getSnapshot(sessionKey) · io.getHistory(sessionKey) · io.listSessions()
|
|
193
|
+
io.listAgentInfo() · io.listModels(agent) · io.listEffort(agent, model?) · io.listTools(agent, workdir?) · io.listSkills(agent, workdir?)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Concurrent prompts to one session **queue** by default (`serialPerSession`, no clobber); `stop`
|
|
197
|
+
interrupts only the current turn and the next queued task promotes.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Drivers (the agent axis)
|
|
202
|
+
|
|
203
|
+
| Driver | id | transport | steer | interact | resume | tui |
|
|
204
|
+
|--------|----|-----------|:-----:|:--------:|:------:|:---:|
|
|
205
|
+
| `ClaudeDriver` | `claude` | `claude` CLI, stream-json (+ `--effort`, partial messages) | ✓ | — | ✓ | ✓ |
|
|
206
|
+
| `CodexDriver` | `codex` | `codex app-server` JSON-RPC (HITL via `requestUserInput`) | ✓ | via askUser | ✓ | ✓ |
|
|
207
|
+
| `GeminiDriver` | `gemini` | `gemini --output-format stream-json` | — | — | ✓ | ✓ |
|
|
208
|
+
| `HermesDriver` | `hermes` | ACP `session/update` | — | — | ✓ | — |
|
|
209
|
+
| `EchoDriver` | `echo` | none (hermetic, in-process) | ✓ | ✓ | ✓ | ✓ |
|
|
210
|
+
|
|
211
|
+
Write your own by implementing `AgentDriver` and passing it to `createLoom({ drivers })` (or
|
|
212
|
+
`loom.registerDriver(...)`). A driver normalizes its agent into `DriverEvent`s:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
type DriverEvent =
|
|
216
|
+
| { type: 'session'; sessionId: string }
|
|
217
|
+
| { type: 'text'; delta: string }
|
|
218
|
+
| { type: 'reasoning'; delta: string }
|
|
219
|
+
| { type: 'tool'; call: UniversalToolCall } // emit on tool start AND completion (status update)
|
|
220
|
+
| { type: 'plan'; plan: UniversalPlan }
|
|
221
|
+
| { type: 'subagent'; subagent: UniversalSubAgent }
|
|
222
|
+
| { type: 'usage'; usage: Partial<UniversalUsage> }
|
|
223
|
+
| { type: 'artifact'; artifact: UniversalArtifact }
|
|
224
|
+
| { type: 'activity'; line: string }; // explicit activity line (drivers that don't emit tool events)
|
|
225
|
+
|
|
226
|
+
interface AgentDriver {
|
|
227
|
+
readonly id: string;
|
|
228
|
+
readonly capabilities?: { steer?: boolean; interact?: boolean; resume?: boolean; tui?: boolean };
|
|
229
|
+
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>; // ctx: { signal, emit, askUser, registerSteer }
|
|
230
|
+
tui?(input: TuiInput): TuiSpec;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Surfaces & Ports
|
|
237
|
+
|
|
238
|
+
**Surfaces** (上层) bind to `LoomIO`. Built in: `WebSurface` (a WebSocket host speaking the
|
|
239
|
+
wire protocol — any pikichannel-style client connects), `CliSurface`. Implement `Surface` to
|
|
240
|
+
add an IM channel or your own UI.
|
|
241
|
+
|
|
242
|
+
**Ports** (side) — all optional, each with a default, so `createLoom()` runs with zero config:
|
|
243
|
+
|
|
244
|
+
| Port | Default | Swap to… |
|
|
245
|
+
|------|---------|----------|
|
|
246
|
+
| `SessionStore` | `FsSessionStore` (`~/.<ns>/sessions`) | your DB / transcript store |
|
|
247
|
+
| `ModelResolver` | `NullModelResolver` (native login) | BYOK credential/provider injection |
|
|
248
|
+
| `ToolProvider` | `NoopToolProvider` | per-session MCP servers |
|
|
249
|
+
| `SystemPromptBuilder` | `PassthroughSystemPromptBuilder` | your system/developer prompt |
|
|
250
|
+
| `Catalog` | `NoopCatalog` | model/effort/tool/skill discovery for composers |
|
|
251
|
+
| `InteractionHandler` | `DeferToTerminalInteractionHandler` | programmatic HITL answers (`AutoCancelInteractionHandler` for one-shots) |
|
|
252
|
+
|
|
253
|
+
**Plugins** are the registration unit for everything ONE capability adds to a session —
|
|
254
|
+
register many, composed deterministically (and dynamically via `loom.registerPlugin(...)`):
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
interface Plugin {
|
|
258
|
+
id: string;
|
|
259
|
+
tools?(opts: { agent; workdir }): McpServerSpec[]; // MCP servers
|
|
260
|
+
promptFragment?(opts: { agent; workdir; isFirstTurn }): string | null; // how-to-use / behavior prompt
|
|
261
|
+
contributeSpawn?(opts: { agent; workdir; mode: 'run'|'tui'; sessionId?; model? }): SpawnContribution | null; // { env?, extraArgs?, configOverrides? }
|
|
262
|
+
decorateSnapshot?(snapshot): UniversalSnapshot;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
67
265
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
266
|
+
The kernel merges contributions per spawn — **never mutating global `process.env`** — in order
|
|
267
|
+
`[ModelResolver → ToolProvider.env → plugins (registration order)]`, so a plugin can override
|
|
268
|
+
the resolver (e.g. point an agent's `ANTHROPIC_BASE_URL` at a local proxy). `promptFragment`s are
|
|
269
|
+
appended to the `SystemPromptBuilder` base and delivered via each agent's native mechanism. This
|
|
270
|
+
is how a capability registers its tools **and** their usage prompt **and** any env/flags in one
|
|
271
|
+
place — and how a model-traffic interceptor injects a redirect on both the `run()` and `tui()`
|
|
272
|
+
rails without the kernel knowing anything about it. (The singular `ModelResolver` /
|
|
273
|
+
`SystemPromptBuilder` ports remain the one authoritative model-credential / base-prompt source;
|
|
274
|
+
plugins are the composable per-capability layer on top.)
|
|
71
275
|
|
|
72
|
-
|
|
276
|
+
---
|
|
73
277
|
|
|
74
|
-
|
|
75
|
-
- Terminals: `WebSurface` (ws host speaking the wire protocol — any pikichannel client connects), `CliSurface`.
|
|
76
|
-
- TUI: `PtyBridge` + `attachTui` + `Loom.runTui/openTui` (raw PTY passthrough with tee).
|
|
77
|
-
- Ports: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`.
|
|
278
|
+
## Exports
|
|
78
279
|
|
|
79
|
-
|
|
280
|
+
Main entry `@pikiloom/kernel` re-exports everything. Subpaths: `@pikiloom/kernel/drivers`,
|
|
281
|
+
`@pikiloom/kernel/surfaces`, `@pikiloom/kernel/protocol`.
|
|
80
282
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
283
|
+
- Runtime: `createLoom`, `Loom`, `Hub`, `SessionRunner`, `runTurn`, `PtyBridge`, `ptyAvailable`, `attachTui`
|
|
284
|
+
- Drivers: `EchoDriver`, `ClaudeDriver`, `CodexDriver`, `GeminiDriver`, `HermesDriver`
|
|
285
|
+
- Surfaces: `WebSurface`, `CliSurface`
|
|
286
|
+
- Ports/defaults: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`, `DeferToTerminalInteractionHandler`, `NoopCatalog`, `defaultBaseDir`
|
|
287
|
+
- Protocol: `UniversalSnapshot`, `diffSnapshot`, `applySnapshotPatch`, `emptySnapshot`, `PROTOCOL_VERSION`, all wire/`Client*`/`Server*` message types
|
|
288
|
+
- Types: `AgentDriver`, `AgentTurnInput`, `DriverContext`, `DriverEvent`, `DriverResult`, `LoomIO`, `PromptInput`, `Surface`, `Plugin`, `SpawnContribution`, `SessionStore`, `ModelResolver`, `ToolProvider`, `SystemPromptBuilder`, `InteractionHandler`, `Catalog`, …
|
|
84
289
|
|
|
85
|
-
|
|
86
|
-
> npm installs (`posix_spawnp failed`). Fix: `chmod +x node_modules/node-pty/prebuilds/*/spawn-helper`.
|
|
290
|
+
---
|
|
87
291
|
|
|
88
|
-
##
|
|
292
|
+
## Verify
|
|
89
293
|
|
|
90
294
|
```bash
|
|
91
295
|
npm run typecheck # tsc, clean
|
|
92
|
-
npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow
|
|
296
|
+
npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow, driver parsers
|
|
93
297
|
KERNEL_E2E_REAL=1 npm test # also drives the real `claude` CLI end-to-end
|
|
94
298
|
node examples/smoke.mjs # smoke against the compiled dist
|
|
95
299
|
```
|
|
96
300
|
|
|
97
|
-
|
|
98
|
-
implemented and **E2E-verified** (hermetic + real-claude + compiled-artifact). The kernel
|
|
99
|
-
lives beside `src/` and does **not** touch the existing pikiloom app.
|
|
301
|
+
See `examples/` for a runnable web console, a Feishu/Lark terminal, and a Node smoke test.
|
|
100
302
|
|
|
101
|
-
##
|
|
303
|
+
## License
|
|
102
304
|
|
|
103
|
-
|
|
104
|
-
2. Promote pikiloom's concrete IM channels to reference `Surface` adapters (`@pikiloom/kernel/surfaces/*`).
|
|
105
|
-
3. Add the full pikichannel transport (WebRTC + rendezvous + TURN + `/api` tunnel) as a `WebSurface` option.
|
|
106
|
-
4. Re-point the pikiloom app's `main.ts` at `createLoom({...})`; keep multi-session queue / promotion / goal
|
|
107
|
-
auto-continuation in the app (not the kernel). Switch over only after parity is proven on DEV.
|
|
305
|
+
MIT
|
|
@@ -65,11 +65,28 @@ export interface Surface {
|
|
|
65
65
|
start(io: LoomIO, host?: TuiHost): Promise<void>;
|
|
66
66
|
stop(): Promise<void>;
|
|
67
67
|
}
|
|
68
|
+
export interface SpawnContribution {
|
|
69
|
+
env?: Record<string, string>;
|
|
70
|
+
extraArgs?: string[];
|
|
71
|
+
configOverrides?: string[];
|
|
72
|
+
}
|
|
68
73
|
export interface Plugin {
|
|
69
74
|
readonly id: string;
|
|
70
75
|
tools?(opts: {
|
|
71
76
|
agent: string;
|
|
72
77
|
workdir: string;
|
|
73
78
|
}): McpServerSpec[] | Promise<McpServerSpec[]>;
|
|
79
|
+
promptFragment?(opts: {
|
|
80
|
+
agent: string;
|
|
81
|
+
workdir: string;
|
|
82
|
+
isFirstTurn: boolean;
|
|
83
|
+
}): string | null | undefined | Promise<string | null | undefined>;
|
|
84
|
+
contributeSpawn?(opts: {
|
|
85
|
+
agent: string;
|
|
86
|
+
workdir: string;
|
|
87
|
+
mode: 'run' | 'tui';
|
|
88
|
+
sessionId?: string | null;
|
|
89
|
+
model?: string | null;
|
|
90
|
+
}): SpawnContribution | null | undefined | Promise<SpawnContribution | null | undefined>;
|
|
74
91
|
decorateSnapshot?(snapshot: UniversalSnapshot): UniversalSnapshot;
|
|
75
92
|
}
|