ispbills-icli 8.4.1 → 8.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ispbills-icli",
3
- "version": "8.4.1",
3
+ "version": "8.4.2",
4
4
  "description": "iCli — IspBills AI network engineer in your terminal",
5
5
  "keywords": [
6
6
  "ispbills",
package/src/client.js CHANGED
@@ -78,7 +78,9 @@ export function parsePartialPlan(raw) {
78
78
  */
79
79
  export async function streamChat(cfg, messages, options = {}) {
80
80
  const { onEvent = () => {}, signal, context = {} } = options;
81
- const body = JSON.stringify({ messages, context });
81
+ // Always identify ourselves as the icli client so the backend can route
82
+ // away from type=connect (which requires an interactive browser terminal).
83
+ const body = JSON.stringify({ messages, context: { ...context, client: 'icli' } });
82
84
 
83
85
  const doFetch = (token) =>
84
86
  fetch(`${cfg.url}/api/v1/ai/chat/stream`, {
package/src/tui/app.js CHANGED
@@ -96,9 +96,11 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion })
96
96
  }, []);
97
97
 
98
98
  const runTurn = useCallback(async (question, opts = {}) => {
99
- const { depth = 0, archive = null, label = '' } = opts;
100
- push({ type: 'user', text: question });
101
- log(`\n${glyphs.prompt} ${question}\n`);
99
+ const { depth = 0, archive = null, label = '', synthetic = false } = opts;
100
+ if (!synthetic) {
101
+ push({ type: 'user', text: question });
102
+ log(`\n${glyphs.prompt} ${question}\n`);
103
+ }
102
104
  convo.current.push({ role: 'user', content: question });
103
105
  setBusy(true);
104
106
  setHint('');
@@ -136,7 +138,19 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion })
136
138
  // so it stays in history instead of vanishing with the live region.
137
139
  if (s.status.length) push({ type: 'activity', status: s.status });
138
140
  if (type === 'plan') { push({ type: 'plan', reply }); log('[plan] ' + (reply.summary || '')); }
139
- else if (type === 'connect') { push({ type: 'connect', reply }); log('[connect] ' + (reply.device?.kind || '')); }
141
+ else if (type === 'connect') {
142
+ push({ type: 'connect', reply }); log('[connect] ' + (reply.device?.kind || ''));
143
+ // The icli has no interactive terminal — send a synthetic session-event
144
+ // so the backend continues to the plan/answer (rule 4). Depth-guard to
145
+ // prevent a loop if the backend keeps emitting connect (shouldn't happen).
146
+ if (depth < 2) {
147
+ const dev = reply.device || {};
148
+ const goal = convo.current.filter((m) => m.role === 'user').slice(-1)[0]?.content || '';
149
+ const sessionEvent = `[session-event] Terminal session opened on ${dev.kind || 'device'} id=${dev.id || '?'} proto=${dev.proto || 'ssh'}. vendor=auto. OPERATOR GOAL: ${goal}`;
150
+ setLive(null); setBusy(false); abortRef.current = null;
151
+ return runTurn(sessionEvent, { depth: depth + 1, synthetic: true });
152
+ }
153
+ }
140
154
  else if (type === 'prompt' || type === 'choice') { push({ type: 'assistant', text: text || reply.question || '' }); log(text || reply.question || ''); }
141
155
  else { push({ type: 'assistant', text: text || '(no response)' }); log(text || ''); }
142
156
  if (text) convo.current.push({ role: 'assistant', content: text });