opencode会话管理

问题背景

使用 opencode 时,退出后会提示一句:

1
2
3
4
5
6
7

█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
█ █ █ █ █▀▀▀ █ █ █ █ █ █ █ █▀▀▀
▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀

Session New session - 2026-05-08T09:32:17.883Z
Continue opencode -s ses_xxxxxxxxxxxxxxxxxxxx

但如果终端窗口关得太快,或忘记复制,这个会话 ID 就丢了,导致无法恢复之前的会话。

我们要实现的就是知道的会话记录,并且可以随时重续之前的会话。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
=== 最近的会话 (第5/13页,共122条) ===
10) [/Users/qian/Downloads] Explore blog structure for Architecture candidates (@explore subagent)
├─ 输入: I need to find files in a Hexo blog that
└─ 最后: I need to find files in a Hexo blog that
9) [/Users/qian/Downloads] Find thread and UI related files (@explore subagent)
├─ 输入: I need to find files in a Hexo blog that
└─ 最后: I need to find files in a Hexo blog that
8) [/Users/qian/Downloads] Check blog cleanup status (@explore subagent)
├─ 输入: Quick exploration: Look at the hexo blog
└─ 最后: Quick exploration: Look at the hexo blog
7) [/Users/qian/Downloads] Explore blog structure (@explore subagent)
├─ 输入: I need to understand the current state o
└─ 最后: I need to understand the current state o
6) [/Users/qian/Downloads] OpenCode 会话管理配置
├─ 输入: 配置opencode会话管理
├─ 倒二: 好
└─ 最后: 提交推送
5) [/Users/qian/Project/dvlproadHexo] Find articles with duplicate titles (@explore subagent)
├─ 输入: Quick search: In /Users/qian/Project/dvl
└─ 最后: Quick search: In /Users/qian/Project/dvl
4) [/Users/qian/Project/dvlproadHexo] Scan all image references (@general subagent)
├─ 输入: Scan ALL markdown files under /Users/qia
└─ 最后: Scan ALL markdown files under /Users/qia
3) [/Users/qian/Project/dvlproadHexo] Check rendered img src for (@explore subagent)
├─ 输入: In the hexo blog at /Users/qian/Project/
└─ 最后: In the hexo blog at /Users/qian/Project/
2) [/Users/qian/Project/dvlproadHexo] Find Plan A vs B discussions (@explore subagent)
├─ 输入: Search thoroughly for any discussion abo
└─ 最后: Search thoroughly for any discussion abo
1) [/Users/qian/Project/AI/AI-qskills] Explore repos data structure (@explore subagent)
├─ 输入: I need to understand the data structure
└─ 最后: I need to understand the data structure
输入序号恢复,n=下一页,p=上一页,直接回车=新会话,q=退出:

阅读全文 →

cc-connect

问题背景

日常开发中,AI 编码工具(如 Claude Code、Cursor)只能在电脑终端使用,离开电脑就无法交互。当你在手机上想让人 AI 执行任务(如查日志、改代码、跑脚本),或者想通过微信/Telegram 远程操控时,就缺少一个桥梁。

cc-connect 正是解决这个问题的工具——它连接 AI 编码工具和即时通讯软件(微信、Telegram),让你在手机上发消息,电脑上的 AI 接收后执行并返回结果。

实现的效果类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cc-connect

2026/05/28 02:28:05 INFO acquired instance lock path=/Users/qian/.cc-connect/.config.toml.lock
2026/05/28 02:28:05 INFO config loaded path=/Users/qian/.cc-connect/config.toml
time=2026-05-28T02:28:05.680+08:00 level=INFO msg="session: loaded from disk" path=/Users/qian/.cc-connect/sessions/my-project_0ff7af6a.json sessions=1
time=2026-05-28T02:28:05.681+08:00 level=INFO msg="session: loaded from disk" path=/Users/qian/.cc-connect/sessions/Users/qian/Project/AI/AIBot/_5bd6edbd.json sessions=1
time=2026-05-28T02:28:05.681+08:00 level=WARN msg="admin_from is not set — privileged commands (/shell, /show, /dir, /restart, /upgrade) are blocked. Set admin_from in config to enable them, or use disabled_commands to hide them." project=/Users/qian/Project/AI/AIBot/
time=2026-05-28T02:28:05.681+08:00 level=INFO msg="platform recovery loop started" project=my-project platform=telegram
time=2026-05-28T02:28:05.681+08:00 level=WARN msg="engine started with partial readiness" project=my-project agent=opencode ready=0 pending=1 failed=0
time=2026-05-28T02:28:05.681+08:00 level=INFO msg="platform ready" project=/Users/qian/Project/AI/AIBot/ platform=weixin
time=2026-05-28T02:28:05.681+08:00 level=INFO msg="engine started" project=/Users/qian/Project/AI/AIBot/ agent=opencode platforms=1
time=2026-05-28T02:28:05.681+08:00 level=INFO msg="cron: scheduler started" jobs=0
time=2026-05-28T02:28:05.682+08:00 level=INFO msg="bridge: server started" addr=:9810 path=/bridge/ws
time=2026-05-28T02:28:05.682+08:00 level=INFO msg="management api started" port=9820
time=2026-05-28T02:28:05.683+08:00 level=INFO msg="api server started" socket=/Users/qian/.cc-connect/run/api.sock
time=2026-05-28T02:28:05.683+08:00 level=INFO msg="cc-connect is running" projects=2
time=2026-05-28T02:28:07.163+08:00 level=INFO msg="telegram: connected" bot=cc_connect_mac_m5_bot
time=2026-05-28T02:28:07.163+08:00 level=INFO msg="platform ready" project=my-project platform=telegram
time=2026-05-28T02:28:07.587+08:00 level=INFO msg="telegram: registered bot commands" count=40
time=2026-05-28T02:30:30.243+08:00 level=INFO msg="message received" platform=telegram msg_id=145 session=telegram:xxxx:yyyy user=roadus content_len=16 has_images=false has_audio=false has_files=false
......

阅读全文 →

日志系统

前言:主要事项

1、异常数据的发现及补充、日志分类优化
2、日志数据的文件保存、滚动存储
3、日志文件上传
4、日志回捞

一、日志收集

1、日志分类

1.1、日志目标

序号 目标(Target)
1 app
2 sdk 三方库
3 h5 网页

1.2、日志类型

序号 类型 描述
1 api_app app中的网络请求
2 api_app_cache app中的网络请求的网络缓存请求
3 api_buriedPoint 埋点的网络请求
4 sdk_other sdk的各种事件(初始化等)
5 sdk_api sdk中的网络请求
6 dart 语法
7 widget 视图(布局像素越界等)
8 click_other 点击、
9 click_share 分享
10 native_route 路由/跳转
11 h5_route 与网页跳转有关
12 h5_js js交互
13 monitor_network 监控:网络类型变化
14 monitor_lifecycle 监控:生命周期变化
15 buriedPoint_other 埋点数据生成等
16 im IM
17 heartbeat 心跳
18 other 其他

2、日志等级

序号 目标(Level) 描述
1 Normal 正常信息(目前用于请求开始)
2 Success 成功信息(目前用于请求结束:成功)
3 Warning 警告信息(目前用于请求结束:报错)
4 Error 错误日志(目前用于请求结束:失败)
5 Dangerous 危险(处理白屏等) 一般会进行额外的埋点

3、日志整合归类

序号 列表 标志 包含
1 全部 all 所有
2 警告 warning 所有的警告
3 错误 error 所有的错误
4 接口 api api_app、api_cache(不包括sdk_api、api_buriedPoint)
5 点击 click click_share、click_other、h5_js
6 路由 route navite_route、h5_route
7 网页 H5 h5_route、h5_js
8 sdk sdk sdk_api、sdk_other
9 code code dart、widget
10 埋点 buriedPoint api_buriedPoint、buriedPoint_other
11 监控 monitor monitor_lifecycle、monitor_network
12 其他 other other
13 api结果 api_result type=api_app & level != Normal
14 im im 本地缓存消息、历史消息、收到的消息等
15 心跳 heartbeat
devtool_log_home_page

4、日志的补充收集及其策略

阅读全文 →

架构模式-③组件化

一、概念了解

1、组件化

就是**”基础库”或者“基础组件”,**意思是把代码重复的部分提炼出一个个组件供给功能使用。

​ 使用:Dialog,各种自定义的UI控件、能在项目或者不同项目重复应用的代码等等。

​ 目的:复用,解耦。

​ 依赖:组件之间低依赖,比较独立。

​ 架构定位:纵向分层(位于架构底层,被其他层所依赖)。

2、模块化

就是**”业务框架”或者“业务模块”**,也可以理解为“框架”,意思是把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。

​ 使用:按照项目功能需求划分成不同类型的业务框架(例如:注册、登录、外卖、直播…..)

​ 目的:隔离/封装 (高内聚)。

​ 依赖:模块之间有依赖的关系,可通过路由器进行模块之间的耦合问题。

​ 架构定位:横向分块(位于架构业务框架层)。

3、总结

​ 其实组件相当于,把一些能在项目里或者不同类型项目中可复用的代码进行工具性的封装。

​ 而模块相应于业务逻辑模块,把同一类型项目里的功能逻辑进行进行需求性的封装。

二、组件化的必要性/产生背景

1、产生背景:

所有模块代码都编写在一个项目中,在项目越来越大后,测试/使用某个模块或功能,需要编译运行整个项目,麻烦

2、组件化思路:

将每个模块作为一个组件,加一个中间层来协调各个模块间的调用,所有的模块间的调用都会经过中间层中转。只让其他模块对中间层产生耦合关系,中间层不对其他模块发生耦合。)

3、组件化好处:

业务划分更佳清晰,新人接手更佳容易,可以按组件分配开发任务。

项目可维护性更强,提高开发效率。

更好排查问题,某个组件出现问题,直接对组件进行处理。

开发测试过程中,可以只编译自己那部分代码,不需要编译整个项目代码。

方便集成,项目需要哪个模块直接通过CocoaPods集成即可。

三、组件化设计思路1:有管理中心

完整的项目:https://github.com/dvlproad/033-Data-Notification-iOS

1、核心思想

启动时候注册关系表,执行时候遍历关系表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 核心思想1:注册,以统一管理,后续调用时候,从注册表中查询并执行对应的操作
void register(url, handle) {
model.url = url;
model.handle = handle;
models.add(model); // 问题1:用什么管理所有数据
}

// 核心思想2:执行,遍历在注册表中的所有操作,找到对应的操作后进行执行
void exec(url) {
for (var model in models) {
if (model.url == url) {
model.handle();
break;
}
}
}

2、实现方案

2.1、方案1:Url+block 路由器

缺点:入参,取参不明显。

2.2、方法2:protocal - class

哈希的本质 哈希冲突的解决:链地址法和开放寻址法。

问:使用多代理模式实现一个类似通知的功能,请问应该用什么来什么结构来存储代理和类的结构?

答:在多代理(Multiple Delegates)模式下,要存储多个代理对象,并且这些代理对象通常是弱引用,以防止循环引用。实现一个类似通知的功能,可以考虑使用 NSPointerArrayNSMapTable 来存储代理对象。如果你只是想管理多个代理对象,NSPointerArray 是更简单的选择。如果你需要将某个对象(如 UIViewController)与多个代理进行关联NSMapTable 更合适,所以我们选择 NSMapTable

将要执行的方法统一放到类中,通过protocal或者类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据 protocal 查找 class
// 方法1(可一对多):为 protocal 添加监听者数组属性(通过runtime,形成类似于 protocal.listeners ),
NSHashTable *protocalListeners = objc_getAssociatedObject(protocol, CJ_BROADCAST_PROTOCOL_LISTENER); // 找到之前绑定到这个protocal上的监听者列表。
objc_setAssociatedObject(protocol, CJ_BROADCAST_PROTOCOL_LISTENER, protocalListeners, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

注册的时候 [protocalListeners addObject:listener];

// 方法2(只能一对一):以 protocal 为 key , 通过 protocal 存取 module 模块(模块自身遵循协议)
// 创建一个NSMapTable实例,其中键是强引用,值是弱引用
NSMapTable<NSString *, NSObject *> *weakValueMapTable = [NSMapTable strongToWeakObjectsMapTable];

// 向哈希表中添加一个键值对
NSString *key = @"someKey";
NSObject *value = [[NSObject alloc] init];
[weakRefHashTable setObject:value forKey:key];

模块协议拆分为多个子协议,用户

网络处理协议、数据管理协议(单独)、用户界面更新协议

改进思想

1
2
3
4
5
6
7
8
9
10
// 1、发起请求(LoginViewController)
id<OrderModulePublic> moduler = [[CJMoudleManager shared] modulerForProtocal:@protocal(OrderModulePublic)];
UIViewController *vc = [moduler orderViewControllerWithPageType:xxx];

// 2、实现请求(TSOrderModuleImpl)
[CJProtocolCenter addModule:self forProtocol:@protocol(OrderModulePublic)];
#pragma mark - OrderModulePublic
- (UIViewController *)orderViewControllerWithPageType:(int)pageType

}

上述代码还可以演变成实现通知的一种形式(当注入的类中,有多个类都遵循了(实际上不应该出现)模块协议时候)

1
2
3
4
5
6
7
8
9
10
11
12
// 1、发起请求(LoginViewController)
NSMutableSet<id<CJUserServiceProtocolForModule>> listener = [[CJMoudleManager shared] listenersForProtocal:@protocal(CJUserServiceProtocolForModule)];
for (id<CJUserServiceProtocolForModule> listener in listeners) {
[listener userNameUpadte:userName];
}

// 2、实现请求(OrderHomeViewController)
[CJProtocolCenter addModule:self forProtocol:@protocol(CJUserServiceProtocolForModule)];
#pragma mark - CJUserServiceProtocolForModule
- (void)userNameUpadte:(NSString *)userName

}

下列图片来源于 《架构分层.graffle》 中的【三、模块间设计】

跳转的使用示例:

modular_CJProtocalManager 跳转

通知的实现示例:

modular_CJProtocalManager 通知

跳转 + 数据变化后内部通知的实现示例:

modular_CJProtocalManager 跳转 + 数据变化后内部通知

四、组件化设计思路2:无管理中心

项目示例:CJStandardProject 中 CTMediator+CJDemoModuleMain.h

1、核心思想

核心思想:直接执行,让系统通过反射来找到要响应事件的类和方法

1
2
3
void exec(target, action, params) {

}

2、实现方案

2.1、方案1:CTMediator target+action+param

实现示例:

modular_CTMediator target+action+param

2.2、方案2:XXXService+Implemention

modular_XXXService+Implemention

五、组件化实施过程的思考

1、架构分层

阅读全文 →

页面加载体验优化

一、页面初始加载优化

1、数据携带/数据参照

通过前一个页面的已获得数据,对所进入的新页面中的数据进行预填充。

eg1:商品列表 —> 商品详情:使用数据携带

eg2:愿望星count个数:通过count的0与非0,知晓所进入的页面初始状态更有可能是哪种状态

1、如果所进入的页面没有缓存数据,则携带的数据在进入的时候直接使用,后台接口返回实时数据后,再更新

2、如果所进入的页面有缓存数据,则携带的数据只能给缓存数据,而不能是后台接口返回的实时数据

2、管理的数据的缓存

2.1、普通数据的缓存

通过缓存框架,将数据缓存起来(key需携带uid),下次界面展示时候,优先从缓存中获取。

附:缓存框架详见:flutter_cache_kit使用文档

2.2、管理数据的缓存

建立Service层,管理用户所有数据的变动。

好处:与普通数据的缓存相比,能够在将来增加数据变动时候,通过本地通知系统,告知相关页面更新相应数据,而不用等到重新下载后才能显示已知道会更新的数据。

使用要点:

修改的时候,同步数据;下次界面需要数据,优先从用户管理服务中获取初始数据

eg1:用户愿望单收藏、商品收藏、品牌收藏、足迹数据

eg2:用户会员中心数据

3、默认数据

如果没有从前一页携带数据过来,则使用与产品约定的默认数据来加载。

1
2
3
if (携带数据) return 携带的.orderCount; 
if (管理数据) return servier.orderCount;
if (默认数据) return default_orderCount;

4、预览页:美团/饿了么的骨架屏灰底效果

对携带的数据与默认数据中相同的对象,使用携带的数据替换掉,一起整合成初始默认数据(携带的数据优先级高)。

5、添加请求加载圈

6、网络接口的缓存数据

页面处理优化及处理过程中的内容(图片)加载优化如下图所示:

图片来源可查看 《app启动与页面加载.graffle》中的【二、页面加载】版面

page_and_image_load_optimize1

从上图中,可以看出弱网情况下,我们可以针对不同的网络:做不同超时,重试设置,以及取不同质量的图片数据。

二、图片优化

图片优化,请查看我的另一篇文章:图片框架.md

图片高磁盘占用,请查看我的另一篇文章:《高磁盘占用的排查与优化.md

三、网络优化

api数据缓存,请查看我的另一篇文章:《网络框架.md

弱网优化,请查看我的另一篇文章:《弱网优化空间探索.md

阅读全文 →

架构模式-①概览

前言:架构模式、框架、设计模式的概念

iOS—架构模式、框架、设计模式的理解

架构模式:架构模式的出现是为了管理复杂的应用程序,这样可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。我们经常说的MVC架构MVVM架构属于此类。

框架:这个最好理解了,通常是代码重用。框架与设计模式的概念容易弄混,两者有相似之处,但却有着根本的不同。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述规范,它比框架更抽象;框架为已经解决问题的具体实现方法,能直接执行或复用;设计模式是比框架更小的元素,一个框架中往往含有一种或多种设计模式。Xcode自带的FoundationUIKit,以及我们经常使用的AFNetworkingMJExtensionSVProgressHUD就属于这一类。

设计模式:设计模式可以通俗的理解为实现/解决某些问题,而形成的解决方案规范。增加代码的可重用性,让代码能更容易理解和可靠。我们通常说所的代理模式迭代器模式策略模式就属于这一类。对各种设计模式的了解可以帮助我们更快的解决编程过程中遇到的问题。

三者关系:架构(动词)>框架>设计模式。

软件通过架构,可以设计出很多不同的框架。在一个框架中,也可以使用很多的设计模式。设计模式不是哪儿哪儿都可以用的,只有当出现了某一特定的问题时,才利用设计模式去解决。设计模式不是用的越多越好,在维护的时候,过多的设计模式会极大的增添维护成本。

一、架构模式

1、架构模式的演变

阅读全文 →

开发工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
背景
一、功能开关
二、app安装包与切换
1、app安装包信息
1.1、换包:app下载页
1.2、版本记录:历史版本记录页
2、各文件目录及大小
三、app环境与切换
1、切换环境
2、切换平台
3、添加代理
四、设备信息
1、设备网络类型
2、设备屏幕大小
2.1、设备适配验证
3、设备ip及代理
4、设备埋点参数
5、是否模拟器
五、用户相关
六、调试页面
1、检查更新
2、网页测试
2.1、网页快捷入口
2.2、白屏测试
2.3、JS测试
2.4、Route测试
七、开发工具安全性处理
八、脚本工具
附1:Charles
附2:代理的实现

背景

问:我不要频繁安装+卸载来安装我想要的包。我能不能同时装一个测试环境和正式环境?

答:不行。安装两个包,严格上来讲,不用想了,不行。具体原因涉及应用id,及app根据该id配置了各种三方key环境(除非你连三方的也都额外提供一套),太详细的道理我就不讲了。”不行“就对了。但是,我能让你安装一个包,却使用不同环境的功能。或者额外安装一个除与应用id相关的功能外其他皆一样的安装包

一、切换环境常见的场景为:

1、测试时候:换环境不用一直下载;

2、演示时候:某个环境使用不了,不用重新下载;

3、抓包时候:想切换代理,不用重新打包安装;

见下文:app环境与切换

二、额外安装一个除与应用id相关的功能外其他皆一样的安装包的使用场景:

1、app中不能接受切换环境,即严格限制一个包只能有一个环境

不允许切换环境的考虑原因:

测试包不允许切到生产:怕脏数据

生产包不允许切到测试:测试环境不是外界用户真正需要的

见我的另一篇文章:《iOS的重签名.md

一、功能开关

tool_entrance_0

tool_entrance_2log

二、app安装包与切换

1、app安装包信息

devtool_appinfo_0package

1.1、换包:app下载页

devtool_appinfo_0package_downloadurl

1.2、版本记录:历史版本记录页

devtool_appinfo_0package_version_records

2、各文件目录及大小

devtool_appinfo_0package_dirsize

三、app环境与切换

package_env_test package_env_product

流程图:《开发工具(含环境切换等).graffle》中的【一、切换环境】版面

devtool_appenv_home

1、切换环境

2、切换平台

3、添加代理

devtool_appenv_3proxy_1 devtool_appenv_3proxy_2

四、设备信息

devtool_device_0devicetoken

devtool_deviceinfo_0

1、设备网络类型

2、设备屏幕大小

2.1、设备适配验证

devtool_device_uiadapt_check

3、设备ip及代理

4、设备埋点参数

未截取到的其他埋点参数见我的另一篇文章:《埋点规范_结构.md

5、是否模拟器

devtool_device_simulator_check

五、用户相关

devtool_user_info

六、调试页面

支付 支付宝
认证 实名认证、真人认证
选择器 生日选择、活动时间选择、项目多选
分享
关键页面入口 个人主页、发布页、引导页、完善性别/生日
位置相关 地图位置选择

快捷入口

1、检查更新

devtool_debug_0checkversion_1

devtool_debug_0checkversion_2

2、网页测试

devtool_debug_2webview_0home

2.1、网页快捷入口

devtool_debug_2webview_1url_entrance

2.2、白屏测试

devtool_debug_2webview_2blank_test.gif

2.3、JS测试

h5js.md

devtool_debug_2webview_3js

2.4、Route测试

h5_open_app.md

devtool_debug_2webview_4route

七、开发工具安全性处理

见我在”安全”专题里的另一篇文章:《安全/开发工具的安全性保障.md

八、脚本工具

目前的脚本操作如下:

qtool_ui_home

其中自定义的脚本操作有如下:

qtool_ui_custom_home

附1:Charles

文档:《Charles.md

附2:代理的实现(Flutter版)

dio添加代理

1
2
3
4
5
6
7
8
9
10
11
   (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
// config the http client
client.findProxy = (uri) {
CommonUtils.log('fcs-myproxy-begin:${uri is Uri} uri:${uri.toString()}');
return "PROXY 192.168.10.47:8888";//如果设置代理(localhost,127.0.0.1这样的是不行的。必须是电脑的ip)
// return 'DIRECT';// 如果不设置代理
};
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;//忽略证书
};